Share via


Hervorheben von Datenpunkten in Power BI-Visuals

In diesem Artikel wird beschrieben, wie Daten in Power BI-Visuals hervorgehoben werden.

Wenn ein Element ausgewählt wird, wird das values-Array im dataView-Objekt standardmäßig so gefiltert, dass nur die ausgewählten Werte angezeigt werden. Wenn das values Array gefiltert wird, zeigen alle anderen Visuals auf der Seite nur die ausgewählten Daten an.

Wenn Sie die supportsHighlight-Eigenschaft in der capabilities.json-Datei auf true festlegen, erhalten Sie ein gänzlich ungefiltertes values-Array zusammen mit einem highlights-Array. Das highlights-Array hat die gleiche Länge wie das Wertarray, und alle nicht ausgewählten Werte werden auf null festgelegt. Ist diese Eigenschaft aktiviert, werden die entsprechenden Daten im Visual hervorgehoben, indem das values-Array mit dem highlights-Array verglichen wird.

Beachten Sie im Beispiel:

  • Ohne Unterstützung von Hervorhebungen ist die Auswahl der einzige Wert im values-Array, und es wird nur ein Balken in der Datenansicht angezeigt.
  • Mit Unterstützung der Hervorhebung befinden sich alle Werte im values-Array. Das highlights-Array enthält für nicht hervorgehobene Elemente den Wert null. In der Datenansicht werden alle Balken angezeigt, der hervorgehobene Balken in einer anderen Farbe.

Auch die Auswahl mehrerer Elemente ist möglich sowie eine partielle Hervorhebung. Die hervorgehobenen Werte werden in der Datenansicht angezeigt.

Hinweis

Die Zuordnung von Tabellendaten in der Ansicht unterstützt das Feature „Highlights“ nicht.

Hervorheben von Datenpunkten mit Kategoriezuordnung in der Datenansicht

Fügen Sie für Visuals mit kategorischer Zuordnung der Datenansicht der Datei "supportsHighlight": true zum capabilities.json hinzu. Zum Beispiel:

{
    "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
}

Nachdem Sie unnötigen Code entfernt haben, sieht der Standardquellcode für das Visual wie im folgenden Beispiel aus:

"use strict";

// ... default imports list

import { FormattingSettingsService } from "powerbi-visuals-utils-formattingmodel";

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

import { VisualFormattingSettingsModel } from "./settings";

export class Visual implements IVisual {
    private target: HTMLElement;
    private formattingSettings: VisualFormattingSettingsModel;
    private formattingSettingsService: FormattingSettingsService;

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

    public update(options: VisualUpdateOptions) {
        this.formattingSettings = this.formattingSettingsService.populateFormattingSettingsModel(VisualFormattingSettingsModel, options.dataViews);
        console.log('Visual update', options);

    }

    // Returns properties pane formatting model content hierarchies, properties and latest formatting values, Then populate properties pane. 
    // This method is called once every time we open properties pane or when the user edit any format property. 
    public getFormattingModel(): powerbi.visuals.FormattingModel {
        return this.formattingSettingsService.buildFormattingModel(this.formattingSettings);
    }
}

Für den Import erforderliche Schnittstellen zum Verarbeiten von Daten aus Power BI:

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

Erstellen des div-Stammelements für Kategoriewerte:

export class Visual implements IVisual {
    private target: HTMLElement;
    private formattingSettings: VisualFormattingSettingsModel;
    private formattingSettingsService: FormattingSettingsService;

    private div: HTMLDivElement; // new property

    constructor(options: VisualConstructorOptions) {
        console.log('Visual constructor', options);
        this.formattingSettingsService = new FormattingSettingsService();
        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);

    }
    // ...
}

Löschen des Inhalts von div-Elementen vor dem Rendern neuer Daten:

// ...
public update(options: VisualUpdateOptions) {
    this.formattingSettings = this.formattingSettingsService.populateFormattingSettingsModel(VisualFormattingSettingsModel, options.dataViews);
    console.log('Visual update', options);

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

Abrufen von Kategorien und Measurewerten aus dem dataView-Objekt:

public update(options: VisualUpdateOptions) {
    this.formattingSettings = this.formattingSettingsService.populateFormattingSettingsModel(VisualFormattingSettingsModel, options.dataViews);
    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;
    // ...
}

Hierbei sind categoryValues ein Array von Kategoriewerten, measureValues ein Array von Measures und measureHighlights die hervorgehobenen Teile von Werten.

Hinweis

Wenn die Werte der measureHighlights-Eigenschaft kleiner als die Werte der categoryValues-Eigenschaft sind, wurde der Wert partiell hervorgehoben.

Aufzählen des categoryValues-Arrays und Abrufen der entsprechenden Werte und Hervorhebungen:

// ...
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);

});

Erstellen der Elemente div und p, um die Werte der Datenansicht im Visual-DOM darzustellen:

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 visualize 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 visualize 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);
});

Anwenden der erforderlichen Formatvorlagen für Elemente, die flexbox verwenden sollen, und Definieren der Farben für div-Elemente:

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;
}

Die resultierende Ansicht des Visuals sieht folgendermaßen aus:

The visuals with categorical data view mapping and highlight

Hervorheben von Datenpunkten mit Zuordnung der Matrixdaten in der Ansicht

Fügen Sie für Visuals mit Zuordnung der Matrixdatenansicht der Datei "supportsHighlight": true zumcapabilities.json hinzu. Zum Beispiel:

{
    "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
}

Die Beispieldaten zum Erstellen einer Hierarchie für die Zuordnung der Matrixdaten in der Ansicht:

Zeile1 Zeile2 Zeile3 Column1 Column2 Column3 Werte
F1 R11 R111 C1 C11 C111 1
F1 R11 R112 C1 C11 C112 2
F1 R11 R113 C1 C11 C113 3
F1 R12 R121 C1 C12 C121 4
F1 R12 R122 C1 C12 C122 5
F1 R12 R123 C1 C12 C123 6
F1 R13 R131 C1 C13 C131 7
F1 R13 R132 C1 C13 C132 8
F1 R13 R133 C1 C13 C133 9
F2 R21 R211 C2 C21 C211 10
F2 R21 R212 C2 C21 C212 11
F2 R21 R213 C2 C21 C213 12
F2 R22 R221 C2 C22 C221 13
F2 R22 R222 C2 C22 C222 14
F2 R22 R223 C2 C22 C223 16
F2 R23 R231 C2 C23 C231 17
F2 R23 R232 C2 C23 C232 18
F2 R23 R233 C2 C23 C233 19

Erstellen Sie das Visual-Standardprojekt, und übernehmen Sie das Beispiel der capabilities.json-Datei.

Nachdem Sie unnötigen Code entfernt haben, sieht der Standardquellcode für das Visual wie im folgenden Beispiel aus:

"use strict";

// ... default imports

import { FormattingSettingsService } from "powerbi-visuals-utils-formattingmodel";
import { VisualFormattingSettingsModel } from "./settings";

export class Visual implements IVisual {
    private target: HTMLElement;
    private formattingSettings: VisualFormattingSettingsModel;
    private formattingSettingsService: FormattingSettingsService;

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

    public update(options: VisualUpdateOptions) {
        this.formattingSettings = this.formattingSettingsService.populateFormattingSettingsModel(VisualFormattingSettingsModel, options.dataViews);
        console.log('Visual update', options);

    }

   /**
     * Returns properties pane formatting model content hierarchies, properties and latest formatting values, Then populate properties pane.
     * This method is called once every time we open properties pane or when the user edit any format property. 
     */
    public getFormattingModel(): powerbi.visuals.FormattingModel {
        return this.formattingSettingsService.buildFormattingModel(this.formattingSettings);
    }
}

Importieren der erforderlichen Schnittstellen zum Verarbeiten von Daten aus Power BI:

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

Erstellen von zwei div-Elementen für das 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";
}

Überprüfen Sie die Daten in der update-Methode, um sicherzustellen, dass das Visual Daten erhält:

public update(options: VisualUpdateOptions) {
    this.formattingSettings = this.formattingSettingsService.populateFormattingSettingsModel(VisualFormattingSettingsModel, options.dataViews);
    console.log('Visual update', options);

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

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

Löschen des Inhalts von div-Elementen vor dem Rendern neuer Daten:

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);
    // ...
}

Erstellen der treeWalker-Funktion zum Durchlaufen der Matrixdatenstruktur:

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

    }
    // ...
}

Dabei ist matrixNode der aktuelle Knoten, levels stellt Metadatenspalten der aktuellen Hierarchieebene dar, div ist das übergeordnete Element für untergeordnete HTML-Elemente.

Die treeWalker-Funktion ist rekursiv, es müssen div- und p-Elemente für den Text als Überschrift erstellt und die Funktion für die untergeordneten Elemente des Knotens aufgerufen werden:

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));
        }
    }
    // ...
}

Aufrufen der Funktion für die Stammelemente von Spalte und Zeile der Matrixstruktur der Datenansicht auf:

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));
}

Generieren Sie die selectionID für Knoten und die Erstellen-Schaltflächen zum Anzeigen von Knoten:

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();
        });
        // ...
    }
    // ...
}

Der Hauptschritt für Hervorhebungen besteht im Erstellen eines weiteren Wertearrays.

Das Objekt des letzten Knotens verfügt über zwei Eigenschaften für das Wertearray, „value“ und „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
  }
 }
}

Dabei stellt value den Wert des Knotens dar, ohne die Auswahl des anderen Visuals anzuwenden, und highlight gibt an, welcher Teil der Daten hervorgehoben wurde.

Hinweis

Ist der Wert von highlight kleiner als der Wert von value, wurde value partiell hervorgehoben.

Fügen Sie Code zum Verarbeiten des values-Arrays des Knotens hinzu, wenn er dargestellt wird:

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) {
            // ...
        }
    }
    // ...
}

Das Ergebnis ist ein Visual mit Schaltflächen und Werten, z. B. highlighted value/default value.

Animation selecting data points on the visual, with matrix data views mapping and highlight.

Nächste Schritte