Επισήμανση σημείων δεδομένων σε απεικονίσεις του Power BI

Αυτό το άρθρο περιγράφει τον τρόπο επισήμανσης δεδομένων σε απεικονίσεις Power BI.

Από προεπιλογή, όταν επιλέγεται ένα στοιχείο, ο values πίνακας στο dataViewαντικείμενο φιλτράρεται ώστε να εμφανίζει μόνο τις επιλεγμένες τιμές. Όταν ο πίνακας values φιλτράρεται, όλες οι άλλες απεικονίσεις στη σελίδα εμφανίζουν μόνο τα επιλεγμένα δεδομένα.

Εάν ορίσετε την supportsHighlight ιδιότητα στο αρχείο σας capabilities.json σε true, θα έχει ως αποτέλεσμα τον πλήρη μη φιλτραρισμένο values πίνακα μαζί με έναν highlights πίνακα. Ο highlights πίνακας έχει το ίδιο μήκος με τον πίνακα τιμών και τυχόν μη επιλεγμένες τιμές ορίζονται σε null. Με ενεργοποιημένη αυτήν την ιδιότητα, επισημαίνονται τα κατάλληλα δεδομένα στην απεικόνιση συγκρίνοντας τον values πίνακα με τον highlights πίνακα.

Στο παράδειγμα, παρατηρήστε ότι:

  • Χωρίς υποστήριξη επισήμανσης, η επιλογή είναι η μοναδική τιμή στον values πίνακα και η μοναδική γραμμή που παρουσιάζεται στην προβολή δεδομένων.
  • Με την υποστήριξη επισήμανσης, όλες οι τιμές βρίσκονται στον values πίνακα. Ο highlights πίνακας περιέχει μια null τιμή για στοιχεία που δεν έχουν επισημανθεί. Όλες οι ράβδοι εμφανίζονται στην προβολή δεδομένων και η επισημασμένη γραμμή έχει διαφορετικό χρώμα.

Μπορεί επίσης να υπάρχουν πολλές επιλογές και μερικές επισημάνσεις. Οι επισημασμένες τιμές παρουσιάζονται στην προβολή δεδομένων.

Σημείωμα

Η αντιστοίχιση προβολής δεδομένων πίνακα δεν υποστηρίζει τη δυνατότητα επισημάνσεων.

Επισήμανση σημείων δεδομένων με αντιστοίχιση προβολής κατηγορικών δεδομένων

Για απεικονίσεις με αντιστοίχιση προβολής κατηγορικών δεδομένων, προσθέστε "supportsHighlight": true στο capabilities.json αρχείο. Για παράδειγμα:

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

Αφού καταργήσετε τον περιττό κώδικα, ο προεπιλεγμένος πηγαίος κώδικας απεικόνισης μοιάζει με το ακόλουθο παράδειγμα:

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

Εισαγάγετε τις απαιτούμενες διασυνδέσεις για την επεξεργασία δεδομένων από το Power BI:

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

Δημιουργήστε το ριζικό div στοιχείο για τις τιμές κατηγορίας:

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

    }
    // ...
}

Διαγράψτε τα περιεχόμενα των στοιχείων div πριν από την απόδοση νέων δεδομένων:

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

Λάβετε τις κατηγορίες και μετρήστε τιμές από το dataView αντικείμενο:

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

Όπου το categoryValues είναι ένας πίνακας τιμών κατηγορίας, measureValues το είναι ένας πίνακας μετρήσεων και measureHighlights το είναι τα τμήματα τιμών που επισημαίνονται.

Σημείωμα

Εάν οι τιμές της measureHighlights ιδιότητας είναι μικρότερες από τις τιμές της categoryValues ιδιότητας, τότε η τιμή επισημάνθηκε μερικώς.

Απαριθμήστε τον categoryValues πίνακα και λάβετε αντίστοιχες τιμές και επισημάνσεις:

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

});

Δημιουργία div και p στοιχεία για την εμφάνιση και απεικόνιση τιμών προβολής δεδομένων στην απεικόνιση 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 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);
});

Εφαρμόστε τα απαιτούμενα στυλ στοιχείων για χρήση flexboxκαι καθορίστε τα χρώματα για στοιχεία div:

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

Το αποτέλεσμα είναι η ακόλουθη προβολή της απεικόνισης:

The visuals with categorical data view mapping and highlight

Επισήμανση σημείων δεδομένων με αντιστοίχιση προβολής δεδομένων μήτρας

Για απεικονίσεις με αντιστοίχιση προβολής δεδομένων μήτρας, προσθέστε "supportsHighlight": true στο capabilities.json αρχείο. Για παράδειγμα:

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

Το δείγμα δεδομένων για τη δημιουργία μιας ιεραρχίας για αντιστοίχιση προβολής δεδομένων μήτρας:

Γραμμή1 Γραμμή2 Γραμμή3 Στήλη1 Στήλη2 Στήλη3 Τιμές
R1 R11 R111 C1 C11 C111 1
R1 R11 R112 C1 C11 C112 2
R1 R11 R113 C1 C11 C113 3
R1 R12 R121 C1 C12 C121 4
R1 R12 R122 C1 C12 C122 5
R1 R12 R123 C1 C12 C123 6
R1 R13 R131 C1 C13 C131 7
R1 R13 R132 C1 C13 C132 8
R1 R13 R133 C1 C13 C133 9
R2 R21 R211 C2 C21 C211 10
R2 R21 R212 C2 C21 C212 11
R2 R21 R213 C2 C21 C213 12
R2 R22 R221 C2 C22 C221 13
R2 R22 R222 C2 C22 C222 14
R2 R22 R223 C2 C22 C223 16
R2 R23 R231 C2 C23 C231 17
R2 R23 R232 C2 C23 C232 18
R2 R23 R233 C2 C23 C233 19

Δημιουργήστε το προεπιλεγμένο έργο απεικόνισης και εφαρμόστε το δείγμα του capabilities.json αρχείου.

Αφού καταργήσετε τον περιττό κώδικα, ο προεπιλεγμένος πηγαίος κώδικας απεικόνισης μοιάζει με το ακόλουθο παράδειγμα:

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

Εισαγάγετε τις απαιτούμενες διασυνδέσεις για την επεξεργασία δεδομένων από το Power BI:

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

Δημιουργήστε δύο div στοιχεία για τη διάταξη απεικόνισης:

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 μέθοδο για να εξασφαλίσετε ότι η απεικόνιση λαμβάνει δεδομένα:

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

Καταργήστε τα περιεχόμενα των στοιχείων πριν από την div απόδοση νέων δεδομένων:

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 συνάρτηση για να διανείμετε τη δομή δεδομένων μήτρας:

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

    }
    // ...
}

Όπου matrixNode το είναι ο τρέχων κόμβος, levels το είναι στήλες μετα-δεδομένων αυτού του επιπέδου ιεραρχίας, div - γονικό στοιχείο για θυγατρικά στοιχεία HTML.

Το treeWalker είναι η επαναλαμβανόμενη συνάρτηση, χρειάζεται να δημιουργήσετε div το στοιχείο και p για το κείμενο ως κεφαλίδα και καλέστε τη συνάρτηση για θυγατρικά στοιχεία του κόμβου:

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

Καλέστε τη συνάρτηση για ριζικά στοιχεία της στήλης και γραμμής της δομής προβολής δεδομένων μήτρας:

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 για κόμβους και δημιουργία κουμπιών για την εμφάνιση κόδων:

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

Το κύριο βήμα της επισήμανσης είναι η δημιουργία ενός άλλου πίνακα τιμών.

Το αντικείμενο του τερματικού κόμβου έχει δύο ιδιότητες για τον πίνακα τιμών, την τιμή και την επισήμανση:

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 υποδεικνύει ποιο τμήμα των δεδομένων έχει επισημανθεί.

Σημείωμα

Εάν η τιμή του highlight είναι μικρότερη από την τιμή του value, τότε value επισημάνθηκε μερικώς.

Προσθέστε κώδικα για να επεξεργαστείτε τον values πίνακα του κόμβου, εάν παρουσιάζεται:

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.

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