Power BI 시각적 개체에서 데이터 요소 강조 표시Highlight data points in Power BI Visuals

기본적으로 요소를 선택할 때마다 dataView 개체의 values 배열이 선택한 값으로만 필터링됩니다.By default whenever an element is selected the values array in the dataView object will be filtered to just the selected values. 그러면 페이지의 다른 모든 시각적 개체에 선택한 데이터만 표시됩니다.It will cause all other visuals on the page to display just the selected data.

강조 표시 ‘dataview’ 기본 동작

capabilities.jsonsupportsHighlight 속성을 true로 설정하면 필터링되지 않은 전체 values 배열과 highlights 배열을 받게 됩니다.If you set the supportsHighlight property in your capabilities.json to true, you'll receive the full unfiltered values array along with a highlights array. highlights 배열의 길이는 values 배열과 같으며, 선택하지 않은 값은 null로 설정됩니다.The highlights array will be the same length as the values array and any non-selected values will be set to null. 이 속성이 사용하도록 설정된 경우, 시각적 개체는 values 배열을 highlights 배열과 비교하여 적절한 데이터를 강조 표시해야 합니다.With this property enabled it's the visual's responsibility to highlight the appropriate data by comparing the values array to the highlights array.

dataview에서 강조 표시 지원

이 예제에서는 막대 1개가 선택된 것을 확인할 수 있습니다.In the example, you'll notice that 1 bar is selected. 또한 highlights 배열의 유일한 값입니다.And it's the only value in the highlights array. 여러 개의 선택 항목과 부분 강조 표시가 있을 수도 있습니다.It's also important to note that there could be multiple selections and partial highlights. 강조 표시된 값이 데이터 뷰에 표시됩니다.The highlighted values will be presented in the data view.

참고

테이블 데이터 뷰 매핑은 하이라이트 기능을 지원하지 않습니다.Table data view mapping doesn't support the highlights feature.

범주 데이터 뷰 매핑을 사용하여 데이터 요소 강조 표시Highlight data points with categorical data view mapping

범주 데이터 뷰 매핑이 있는 시각적 개체는 capabilities.json"supportsHighlight": true 매개 변수를 포함합니다.The visuals with categorical data view mapping have capabilities.json with "supportsHighlight": true parameter. 예:For example:

{
    "dataRoles": [
        {
            "displayName": "Category",
            "name": "category",
            "kind": "Grouping"
        },
        {
            "displayName": "Value",
            "name": "value",
            "kind": "Measure"
        }
    ],
    "dataViewMappings": [
        {
            "categorical": {
                "categories": {
                    "for": {
                        "in": "category"
                    }
                },
                "values": {
                    "for": {
                        "in": "value"
                    }
                }
            }
        }
    ],
    "supportsHighlight": true
}

불필요한 코드를 제거한 후의 기본 시각적 개체 소스 코드는 다음과 같이 표시됩니다.The default visual source code after removing unnecessary code will look like this:

"use strict";

// ... default imports list

import DataViewCategorical = powerbi.DataViewCategorical;
import DataViewCategoryColumn = powerbi.DataViewCategoryColumn;
import PrimitiveValue = powerbi.PrimitiveValue;
import DataViewValueColumn = powerbi.DataViewValueColumn;

import { VisualSettings } from "./settings";

export class Visual implements IVisual {
    private target: HTMLElement;
    private settings: VisualSettings;

    constructor(options: VisualConstructorOptions) {
        console.log('Visual constructor', options);
        this.target = options.element;
        this.host = options.host;

    }

    public update(options: VisualUpdateOptions) {
        this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);
        console.log('Visual update', options);

    }

    private static parseSettings(dataView: DataView): VisualSettings {
        return <VisualSettings>VisualSettings.parse(dataView);
    }

    /**
     * This function gets called for each of the objects defined in the capabilities files and allows you to select which of the
     * objects and properties you want to expose to the users in the property pane.
     *
     */
    public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {
        return VisualSettings.enumerateObjectInstances(this.settings || VisualSettings.getDefault(), options);
    }
}

Power BI에서 데이터를 처리하는 데 필요한 인터페이스를 가져옵니다.Import required interfaces to process data from Power BI:

import DataViewCategorical = powerbi.DataViewCategorical;
import DataViewCategoryColumn = powerbi.DataViewCategoryColumn;
import PrimitiveValue = powerbi.PrimitiveValue;
import DataViewValueColumn = powerbi.DataViewValueColumn;

범주 값에 대해 루트 div 요소를 만듭니다.Create root div element for category values:

export class Visual implements IVisual {
    private target: HTMLElement;
    private settings: VisualSettings;

    private div: HTMLDivElement; // new property

    constructor(options: VisualConstructorOptions) {
        console.log('Visual constructor', options);
        this.target = options.element;
        this.host = options.host;

        // create div element
        this.div = document.createElement("div");
        this.div.classList.add("vertical");
        this.target.appendChild(this.div);

    }
    // ...
}

새 데이터를 렌더링하기 전에 div 요소의 콘텐츠를 지웁니다.Clear content of div elements before rendering new data:

// ...
public update(options: VisualUpdateOptions) {
    this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);
    console.log('Visual update', options);

    while (this.div.firstChild) {
        this.div.removeChild(this.div.firstChild);
    }
    // ...
}

dataView 개체에서 범주와 측정값을 가져옵니다.Get categories and measure values from dataView object:

public update(options: VisualUpdateOptions) {
    this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);
    console.log('Visual update', options);

    while (this.div.firstChild) {
        this.div.removeChild(this.div.firstChild);
    }

    const dataView: DataView = options.dataViews[0];
    const categoricalDataView: DataViewCategorical = dataView.categorical;
    const categories: DataViewCategoryColumn = categoricalDataView.categories[0];
    const categoryValues = categories.values;

    const measures: DataViewValueColumn = categoricalDataView.values[0];
    const measureValues = measures.values;
    const measureHighlights = measures.highlights;
    // ...
}

여기서 categoryValues는 범주 값 배열, measureValues는 측정값 배열, measureHighlights는 강조 표시된 값 부분입니다.Where categoryValues is an array of category values, measureValues is an array of measures, and measureHighlights is highlighted parts of values.

참고

measureHighlights 속성 값은 categoryValues 속성 값보다 작을 수 있습니다.Values of measureHighlights property can be less that values of categoryValues property. 값이 부분적으로 강조 표시되었음을 의미합니다.In means that value was higlighted partially.

categoryValues 배열을 열거하고 해당 값과 강조 표시를 가져옵니다.Enumerate categoryValues array and get corresponding values and highlights:

// ...
const measureHighlights = measures.highlights;

categoryValues.forEach((category: PrimitiveValue, index: number) => {
    const measureValue = measureValues[index];
    const measureHighlight = measureHighlights && measureHighlights[index] ? measureHighlights[index] : null;
    console.log(category, measureValue, measureHighlight);

});

divp 요소를 만들어 시각적 개체 DOM에서 데이터 뷰 값을 표시하고 시각화합니다.Create div and p elements to display and visualize data view values in visual DOM:

categoryValues.forEach((category: PrimitiveValue, index: number) => {
    const measureValue = measureValues[index];
    const measureHighlight = measureHighlights && measureHighlights[index] ? measureHighlights[index] : null;
    console.log(category, measureValue, measureHighlight);

    // div element. it contains elements to display values and visualize value as progress bar
    let div = document.createElement("div");
    div.classList.add("horizontal");
    this.div.appendChild(div);

    // div element to vizualize value of measure
    let barValue = document.createElement("div");
    barValue.style.width = +measureValue * 10 + "px";
    barValue.style.display = "flex";
    barValue.classList.add("value");

    // element to display category value
    let bp = document.createElement("p");
    bp.innerText = category.toString();

    // div element to vizualize highlight of measure
    let barHighlight = document.createElement("div");
    barHighlight.classList.add("highlight")
    barHighlight.style.backgroundColor = "blue";
    barHighlight.style.width = +measureHighlight * 10 + "px";

    // element to display highlighted value of measure
    let p = document.createElement("p");
    p.innerText = `${measureHighlight}/${measureValue}`;
    barHighlight.appendChild(bp);

    div.appendChild(barValue);

    barValue.appendChild(barHighlight);
    div.appendChild(p);
});

요소에 필요한 스타일을 적용하여 flex box를 사용하고 div 요소의 색을 정의합니다.Apply required styles for elements to use flex box and define colors for div elements:

div.vertical {
    display: flex;
    flex-direction: column;
}

div.horizontal {
    display: flex;
    flex-direction: row;
}

div.highlight {
    background-color: blue
}

div.value {
    background-color: red;
    display: flex;
}

결과에는 다음과 같은 시각적 개체 뷰가 있어야 합니다.In the result, you should have the following view of the visual.

범주 데이터 뷰 매핑과 강조 표시가 있는 시각적 개체

행렬 데이터 뷰 매핑을 사용하여 데이터 요소 강조 표시Highlight data points with matrix data view mapping

행렬 데이터 뷰 매핑이 있는 시각적 개체는 capabilities.json"supportsHighlight": true 매개 변수를 포함합니다.The visuals with matrix data view mapping have capabilities.json with "supportsHighlight": true parameter. 예:For example:

{
    "dataRoles": [
        {
            "displayName": "Columns",
            "name": "columns",
            "kind": "Grouping"
        },
        {
            "displayName": "Rows",
            "name": "rows",
            "kind": "Grouping"
        },
        {
            "displayName": "Value",
            "name": "value",
            "kind": "Measure"
        }
    ],
    "dataViewMappings": [
        {
            "matrix": {
                "columns": {
                    "for": {
                        "in": "columns"
                    }
                },
                "rows": {
                    "for": {
                        "in": "rows"
                    }
                },
                "values": {
                    "for": {
                        "in": "value"
                    }
                }
            }
        }
    ],
    "supportsHighlight": true
}

행렬 데이터 뷰 매핑을 위한 계층 구조를 만드는 샘플 데이터는 다음과 같습니다.The sample data to create hierarchy for matrix data view mapping:

Row1Row1 Row2Row2 Row3Row3 열1Column1 Column2Column2 열3Column3 Values
R1R1 R11R11 R111R111 C1C1 C11C11 C111C111 11
R1R1 R11R11 R112R112 C1C1 C11C11 C112C112 22
R1R1 R11R11 R113R113 C1C1 C11C11 C113C113 33
R1R1 R12R12 R121R121 C1C1 C12C12 C121C121 44
R1R1 R12R12 R122R122 C1C1 C12C12 C122C122 55
R1R1 R12R12 R123R123 C1C1 C12C12 C123C123 66
R1R1 R13R13 R131R131 C1C1 C13C13 C131C131 77
R1R1 R13R13 R132R132 C1C1 C13C13 C132C132 88
R1R1 R13R13 R133R133 C1C1 C13C13 C133C133 99
R2R2 R21R21 R211R211 C2C2 C21C21 C211C211 1010
R2R2 R21R21 R212R212 C2C2 C21C21 C212C212 1111
R2R2 R21R21 R213R213 C2C2 C21C21 C213C213 1212
R2R2 R22R22 R221R221 C2C2 C22C22 C221C221 1313
R2R2 R22R22 R222R222 C2C2 C22C22 C222C222 1414
R2R2 R22R22 R223R223 C2C2 C22C22 C223C223 1616
R2R2 R23R23 R231R231 C2C2 C23C23 C231C231 1717
R2R2 R23R23 R232R232 C2C2 C23C23 C232C232 1818
R2R2 R23R23 R233R233 C2C2 C23C23 C233C233 1919

기본 시각적 개체 프로젝트를 만들고 capabilities.json 샘플을 적용합니다.Create the default visual project and apply sample of capabilities.json.

불필요한 코드를 제거한 후의 기본 시각적 개체 소스 코드는 다음과 같이 표시됩니다.Default visual source code after removing unessesray code will look:

"use strict";

// ... default imports

import { VisualSettings } from "./settings";

export class Visual implements IVisual {
    private target: HTMLElement;
    private settings: VisualSettings;


    constructor(options: VisualConstructorOptions) {
        console.log('Visual constructor', options);
        this.target = options.element;
        this.host = options.host;
    }

    public update(options: VisualUpdateOptions) {
        this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);
        console.log('Visual update', options);

    }

    private static parseSettings(dataView: DataView): VisualSettings {
        return <VisualSettings>VisualSettings.parse(dataView);
    }

    /**
     * This function gets called for each of the objects defined in the capabilities files and allows you to select which of the
     * objects and properties you want to expose to the users in the property pane.
     *
     */
    public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {
        return VisualSettings.enumerateObjectInstances(this.settings || VisualSettings.getDefault(), options);
    }
}

Power BI에서 데이터를 처리하는 데 필요한 인터페이스를 가져옵니다.Import required interfaces to process data from Power BI:

import DataViewMatrix = powerbi.DataViewMatrix;
import DataViewMatrixNode = powerbi.DataViewMatrixNode;
import DataViewHierarchyLevel = powerbi.DataViewHierarchyLevel;

시각적 개체 레이아웃에 사용할 div 요소 2개를 만듭니다.Create two div elements for visual layout:

constructor(options: VisualConstructorOptions) {
    // ...
    this.rowsDiv = document.createElement("div");
    this.target.appendChild(this.rowsDiv);

    this.colsDiv = document.createElement("div");
    this.target.appendChild(this.colsDiv);
    this.target.style.overflowY = "auto";
}

update 메서드의 데이터를 검사하여 시각적 개체가 데이터를 가져오는지 확인합니다.Check the data in update method, to ensure that visual gets data:

public update(options: VisualUpdateOptions) {
    this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);
    console.log('Visual update', options);

    const dataView: DataView = options.dataViews[0];
    const matrixDataView: DataViewMatrix = dataView.matrix;

    if (!matrixDataView ||
        !matrixDataView.columns ||
        !matrixDataView.rows ) {
        return
    }
    // ...
}

새 데이터를 렌더링하기 전에 div 요소의 콘텐츠를 지웁니다.Clear content of div elements before render new data:

public update(options: VisualUpdateOptions) {
    // ...

    // remove old elements
    // to better performance use D3js pattern:
    // https://d3js.org/#enter-exit
    while (this.rowsDiv.firstChild) {
        this.rowsDiv.removeChild(this.rowsDiv.firstChild);
    }
    const prow = document.createElement("p");
    prow.innerText = "Rows";
    this.rowsDiv.appendChild(prow);

    while (this.colsDiv.firstChild) {
        this.colsDiv.removeChild(this.colsDiv.firstChild);
    }
    const pcol = document.createElement("p");
    pcol.innerText = "Columns";
    this.colsDiv.appendChild(pcol);
    // ...
}

행렬 데이터 구조를 트래버스하는 treeWalker 함수를 만듭니다.Create treeWalker function to traverse matrix data structure:

public update(options: VisualUpdateOptions) {
    // ...
    const treeWalker = (matrixNode: DataViewMatrixNode, index: number, levels: DataViewHierarchyLevel[], div: HTMLDivElement)  => {

    }
    // ...
}

여기서 matrixNode는 현재 노드, div는 이 계층 구조 수준의 메타데이터 열, levels는 자식 HTML 요소의 부모 요소입니다.Where matrixNode is the current node, levels is metadata columns of this hierarchy level, div - parent element for child HTML elements.

treeWalker는 재귀 함수로, div 요소와 헤더 텍스트를 나타내는 p를 만들고 노드의 자식 요소에 대해 함수를 호출해야 합니다.The treeWalker is recursive function, need to create div element and p for text as header, and call the function for child elements of node:

public update(options: VisualUpdateOptions) {
    // ...
    const treeWalker = (matrixNode: DataViewMatrixNode, index: number, levels: DataViewHierarchyLevel[], div: HTMLDivElement)  => {
        // ...

        if (matrixNode.children) {
            const childDiv = document.createElement("div");
            childDiv.classList.add("vertical");
            div.appendChild(childDiv);

            const p = document.createElement("p");
            const level = levels[matrixNode.level]; // get current level column metadata from current node
            p.innerText = level.sources[level.sources.length - 1].displayName; // get column name from metadata

            childDiv.appendChild(p); // add paragraph element to div element
            matrixNode.children.forEach((node, index) => treeWalker(node, levels, childDiv, ++levelIndex));
        }
    }
    // ...
}

행렬 데이터 뷰 구조의 열 및 행 루트 요소에 대해 함수를 호출합니다.Call the function for root elements of column and row of matrix data view structure:

public update(options: VisualUpdateOptions) {
    // ...
    const treeWalker = (matrixNode: DataViewMatrixNode, index: number, levels: DataViewHierarchyLevel[], div: HTMLDivElement)  => {
        // ...
    }
    // ...
    // remove old elements
    // ...

    // ...
    const rowRoot: DataViewMatrixNode = matrixDataView.rows.root;
    rowRoot.children.forEach((node) => treeWalker(node, matrixDataView.rows.levels, this.rowsDiv));

    const colRoot = matrixDataView.columns.root;
    colRoot.children.forEach((node) => treeWalker(node, matrixDataView.columns.levels, this.colsDiv));
}

노드의 selectionID를 생성하고 노드를 표시하는 단추를 만듭니다.Generate selectionID for nodes and Create buttons to display nodes:

public update(options: VisualUpdateOptions) {
    // ...
    const treeWalker = (matrixNode: DataViewMatrixNode, index: number, levels: DataViewHierarchyLevel[], div: HTMLDivElement)  => {
        const selectionID: ISelectionID = this.host.createSelectionIdBuilder()
            .withMatrixNode(matrixNode, levels)
            .createSelectionId();

        let nodeBlock = document.createElement("button");
        nodeBlock.innerText = matrixNode.value.toString();

        nodeBlock.addEventListener("click", (event) => {
            // call select method in the selection manager
            this.selectionManager.select(selectionID);
        });

        nodeBlock.addEventListener("contextmenu", (event) => {
            // call showContextMenu method to display context menu on the visual
            this.selectionManager.showContextMenu(selectionID, {
                x: event.clientX,
                y: event.clientY
            });
            event.preventDefault();
        });
        // ...
    }
    // ...
}

강조 표시를 사용하는 주요 단계는 추가 값 배열을 처리하는 것입니다.The main step of using highlighting is to process additional array of values.

터미널 노드의 개체를 검사하면 값 배열에 value와 highlight라는 두 개의 속성이 있는 것을 확인할 수 있습니다.If you inspect the object of terminal node, you can see that the values array has two properties - value and highlight:

JSON.stringify(options.dataViews[0].matrix.rows.root.children[0].children[0].children[0], null, " ");
{
 "level": 2,
 "levelValues": [
  {
   "value": "R233",
   "levelSourceIndex": 0
  }
 ],
 "value": "R233",
 "identity": {
  "identityIndex": 2
 },
 "values": {
  "0": {
   "value": null,
   "highlight": null
  },
  "1": {
   "value": 19,
   "highlight": 19
  }
 }
}

여기서 value 속성은 다른 시각적 개체의 선택 항목을 적용하지 않고 노드 값을 나타내고, highlight 속성은 강조 표시된 데이터 부분을 나타냅니다.Where value property represents value of node without applying a selection from other visual, and highlight property indicates which part of data was highlighted.

참고

highlight 속성 값은 value 속성 값보다 작을 수 있습니다.Value of highlight property can be less that value of value property. 값이 부분적으로 강조 표시되었음을 의미합니다.In means that value was higlighted partially.

노드의 values 배열을 처리하는 코드를 추가합니다(표시되는 경우).Add the code to process the values array of node if it is presented:

public update(options: VisualUpdateOptions) {
    // ...
    const treeWalker = (matrixNode: DataViewMatrixNode, index: number, levels: DataViewHierarchyLevel[], div: HTMLDivElement)  => {
        // ...

        if (matrixNode.values) {
            const sumOfValues = Object.keys(matrixNode.values) // get key property of object (value are 0 to N)
                .map(key => +matrixNode.values[key].value) // convert key property to number
                .reduce((prev, curr) => prev + curr) // sum of values

            let sumOfHighlights = sumOfValues;
            sumOfHighlights = Object.keys(matrixNode.values) // get key property of object (value are 0 to N)
                .map(key => matrixNode.values[key].highlight ? +matrixNode.values[key].highlight : null ) // convert key property to number if it exists
                .reduce((prev, curr) => curr ? prev + curr : null) // convert key property to number

            // create div container for value and highlighted value
            const vals = document.createElement("div");
            vals.classList.add("vertical")
            vals.classList.replace("vertical", "horizontal");
            // create paragraph element for label
            const highlighted = document.createElement("p");
            // Display complete value and highlighted value
            highlighted.innerText = `${sumOfHighlights}/${sumOfValues}`;

            // create div container for value
            const valueDiv = document.createElement("div");
            valueDiv.style.width = sumOfValues * 10 + "px";
            valueDiv.classList.add("value");

            // create div container for highlighted values
            const highlightsDiv = document.createElement("div");
            highlightsDiv.style.width = sumOfHighlights * 10 + "px";
            highlightsDiv.classList.add("highlight");
            valueDiv.appendChild(highlightsDiv);

            // append button and paragraph to div containers to parent div
            vals.appendChild(nodeBlock);
            vals.appendChild(valueDiv);
            vals.appendChild(highlighted);
            div.appendChild(vals);
        } else {
            div.appendChild(nodeBlock);
        }

        if (matrixNode.children) {
            // ...
        }
    }
    // ...
}

따라서 단추와 highlighted value/default value 값을 포함하는 시각적 개체를 얻게 됩니다.As the result you'll get the visual with buttons and values highlighted value/default value

행렬 데이터 뷰 매핑과 강조 표시가 있는 시각적 개체

다음 단계Next steps