Hinzufügen von Unterstützung für Drilldowns

Wenn ein Visual eine Hierarchie aufweist, können Sie Benutzern die Verwendung des Power BI-Drilldownfeatures ermöglichen, damit sie weitere Details anzeigen können.

Weitere Informationen zum Power BI-Drilldownfeature finden Sie in Drill-Modus in dem Power BI-Dienst. Informationen dazu, wie das Visual das Drillfeature dynamisch aktivieren oder deaktivieren kann, finden Sie unter Dynamisches Steuerelement für den Drilldown.

Aktivieren der Drilldownunterstützung im Visual

Um Drilldownaktionen im Visual zu unterstützen, fügen Sie capabilities.json ein neues Feld namens drill-down hinzu. Dieses Feld hat eine roles-Eigenschaft, die den Namen der dataRole enthält, für die Drilldownaktionen aktiviert werden sollen.

    "drilldown": {
        "roles": [
            "category"
        ]
    }

Hinweis

Der Drilldown „dataRole“ muss vom Typ Grouping sein. Die max-Eigenschaft in den dataRole-Bedingungen muss auf 1 gesetzt sein.

Nachdem Sie dem Drilldownfeld die Rolle hinzugefügt haben, können Benutzer mehrere Felder in die Datenrolle ziehen.

Beispiel:

{
    "dataRoles": [
        {
            "displayName": "Category",
            "name": "category",
            "kind": "Grouping"
        },
        {
            "displayName": "Value",
            "name": "value",
            "kind": "Measure"
        }
    ],
    "drilldown": {
        "roles": [
            "category"
        ]
    },
    "dataViewMappings": [
        {
            "categorical": {
                "categories": {
                    "for": {
                        "in": "category"
                    }
                },
                "values": {
                    "select": [
                        {
                            "bind": {
                                "to": "value"
                            }
                        }
                    ]
                }
            }
        }
    ]
}

Erstellen eines Visuals mit Drilldown-Unterstützung

Führen Sie den folgenden Befehl aus, um ein visuelles Element mit Drilldown-Unterstützung zu erstellen:

pbiviz new testDrillDown -t default

Wenden Sie dann das obige Beispiel von capabilities.json auf das neu erstellte Visual an, um ein neues Standard-Beispielvisual zu erstellen.

Erstellen Sie die Eigenschaft für div-Container, um HTML-Elemente des Visuals zu speichern:

"use strict";

import "core-js/stable";
import "./../style/visual.less";
// imports

export class Visual implements IVisual {
    // visual properties
    // ...
    private div: HTMLDivElement; // <== NEW PROPERTY

    constructor(options: VisualConstructorOptions) {
        // constructor body
        // ...
    }

    public update(options: VisualUpdateOptions) {
        // update method body
        // ...
    }

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

Aktualisieren Sie den Konstruktor des Visuals:


export class Visual implements IVisual {
    // visual properties
    // ...
    private div: HTMLDivElement;

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

        if (document) {
            const new_p: HTMLElement = document.createElement("p");
            new_p.appendChild(document.createTextNode("Update count:"));
            const new_em: HTMLElement = document.createElement("em");
            this.textNode = document.createTextNode(this.updateCount.toString());
            new_em.appendChild(this.textNode);
            new_p.appendChild(new_em);
            this.div = document.createElement("div"); // <== CREATE DIV ELEMENT
            this.target.appendChild(new_p);
        }
    }
}

Um button zu erstellen, aktualisieren Sie mehrere update-Methode des visuellen Elements:

export class Visual implements IVisual {
    // ...

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

        const dataView: DataView = options.dataViews[0];
        const categoricalDataView: DataViewCategorical = dataView.categorical;

        // don't create elements if no data
        if (!options.dataViews[0].categorical ||
            !options.dataViews[0].categorical.categories) {
            return
        }

        // to display current level of hierarchy
        if (typeof this.textNode !== undefined) {
            this.textNode.textContent = categoricalDataView.categories[categoricalDataView.categories.length - 1].source.displayName.toString();
        }

        // remove old elements
        // for better performance use D3js pattern:
        // https://d3js.org/#enter-exit
        while (this.div.firstChild) {
            this.div.removeChild(this.div.firstChild);
        }

        // create buttons for each category value
        categoricalDataView.categories[categoricalDataView.categories.length - 1].values.forEach( (category: powerbi.PrimitiveValue, index: number) => {
            let button = document.createElement("button");
            button.innerText = category.toString();

            this.div.appendChild(button);
        })

    }
    // ...

Wenden Sie einfache Stile in .\style\visual.less an:

button {
    margin: 5px;
    min-width: 50px;
    min-height: 50px;
}

Bereiten Sie Beispieldaten zum Testen des Visuals vor:

H1 H2 H3 VALUES
A A1 A11 1
A A1 A12 2
A A2 A21 3
A A2 A22 4
A A3 A31 5
A A3 A32 6
B B1 B11 7
B B1 B12 8
B B2 B21 9
B B2 B22 10
B B3 B31 11
B B3 B32 12

Und erstellen Sie eine Hierarchie in Power BI Desktop:

Der Screenshot zeigt den Power BI-Desktop mit „Neue Hierarchie“ in einem Kontextmenü ausgewählt.

Schließen Sie alle Kategoriespalten (H1, H2, H3) in die neue Hierarchie ein:

Der Screenshot zeigt Kategoriespalten, die Sie der neuen Hierarchie hinzufügen können.

Nachdem Sie diese Schritte ausgeführt haben, sollten Sie Folgendes Visual erhalten:

Dev-Visual mit Schaltflächen

Hinzufügen eines Kontextmenüs zu visuellen Elementen

Um ein Kontextmenü zu den Schaltflächen von dem visuellen Element hinzuzufügen:

Kontextmenü im Visual

Speichern Sie das host-Objekt in den Eigenschaften des Visuals. Rufen Sie dann die createSelectionManager-Methode zum Erstellen des Auswahl-Managers auf, um ein Kontextmenü mit der Power BI-Visuals-API anzuzeigen.

"use strict";

import "core-js/stable";
import "./../style/visual.less";
// default imports

import IVisualHost = powerbi.extensibility.visual.IVisualHost;
import ISelectionManager = powerbi.extensibility.ISelectionManager;
import ISelectionId = powerbi.visuals.ISelectionId;

export class Visual implements IVisual {
    // visual properties
    // ...
    private div: HTMLDivElement;
    private host: IVisualHost; // <== NEW PROPERTY
    private selectionManager: ISelectionManager; // <== NEW PROPERTY

    constructor(options: VisualConstructorOptions) {
        // constructor body
        // save the host in the visuals properties
        this.host = options.host;
        // create selection manager
        this.selectionManager = this.host.createSelectionManager();
        // ...
    }

    public update(options: VisualUpdateOptions) {
        // update method body
        // ...
    }

    // ...
}

Ändern Sie den Text des forEach-Funktionsrückrufs in:

    categoricalDataView.categories[categoricalDataView.categories.length - 1].values.forEach( (category: powerbi.PrimitiveValue, index: number) => {
        // create selectionID for each category value
        let selectionID: ISelectionId = this.host.createSelectionIdBuilder()
            .withCategory(categoricalDataView.categories[0], index)
            .createSelectionId();

        let button = document.createElement("button");
        button.innerText = category.toString();

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

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

        this.div.appendChild(button);
    });

Wenden Sie Daten auf das Visual an:

Der Screenshot zeigt die Hierarchie mit H2 als Legende.

Im letzten Schritt sollte das Visual mit Auswahlmöglichkeiten und einem Kontextmenü angezeigt werden:

Die Animation zeigt das Auswählen von Drilldown und Drillup im Kontextmenü des Visuals.

Hinzufügen von Drilldownunterstützung für die Zuordnung der Matrixdatenansicht

Um das Visual mit Zuordnungen der Matrixdatenansicht zu testen, bereiten Sie Beispieldaten vor:

Row 1 Zeile 2 Zeile 3 Spalte 1 Spalte 2 Spalte 3 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

Wenn Sie dann die folgende Zuordnung der Datenansicht auf das Visual an:

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

Wenden Sie Daten auf das Visual an:

Der Screenshot zeigt die MatrixHierarchy mit der ausgewählten Spalten- und Zeilenhierarchie und ihren Mitgliedern.

Importieren Sie die erforderlichen Schnittstellen, um Zuordnungen der Matrixdatenansicht zu verarbeiten:

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

Erstellen Sie zwei Eigenschaften für zwei div-Zeilen- und -Spaltenelemente:

export class Visual implements IVisual {
    // ...
    private rowsDiv: HTMLDivElement;
    private colsDiv: HTMLDivElement;
    // ...
    constructor(options: VisualConstructorOptions) {
        // constructor body
        // ...
        // Create div elements and append to main div of the visual
        this.rowsDiv = document.createElement("div");
        this.target.appendChild(this.rowsDiv);

        this.colsDiv = document.createElement("div");
        this.target.appendChild(this.colsDiv);
    }
    // ...
}

Überprüfen Sie die Daten vor dem Rendern von Elementen, und zeigen Sie die aktuelle Hierarchieebene an:

export class Visual implements IVisual {
    // ...
    constructor(options: VisualConstructorOptions) {
        // constructor body
    }

    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 the visual doesn't receive the data no reason to continue rendering
        if (!matrixDataView ||
            !matrixDataView.columns ||
            !matrixDataView.rows ) {
            return
        }

        // to display current level of hierarchy
        if (typeof this.textNode !== undefined) {
            this.textNode.textContent = categoricalDataView.categories[categoricalDataView.categories.length - 1].source.displayName.toString();
        }
        // ...
    }
    // ...
}

Erstellen Sie die Funktion treeWalker zum Durchlaufen der Hierarchie:

export class Visual implements IVisual {
    // ...
    public update(options: VisualUpdateOptions) {
        // ...

        // if the visual doesn't receive the data no reason to continue rendering
        if (!matrixDataView ||
            !matrixDataView.columns ||
            !matrixDataView.rows ) {
            return
        }

        const treeWalker = (matrixNode: DataViewMatrixNode, index: number, levels: DataViewHierarchyLevel[], div: HTMLDivElement)  => {
            // ...
            if (matrixNode.children) {
                // ...
                // traversing child nodes
                matrixNode.children.forEach((node, index) => treeWalker(node, index, levels, childDiv));
            }
        }

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

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

Generieren Sie die Auswahl für Datenpunkte.

const treeWalker = (matrixNode: DataViewMatrixNode, index: number, levels: DataViewHierarchyLevel[], div: HTMLDivElement)  => {
    // generate selectionID for each node of matrix
    const selectionID: ISelectionID = this.host.createSelectionIdBuilder()
        .withMatrixNode(matrixNode, levels)
        .createSelectionId();
    // ...
    if (matrixNode.children) {
        // ...
        // traversing child nodes
        matrixNode.children.forEach((node, index) => treeWalker(node, index, levels, childDiv));
    }
}

Erstellen Sie für jede Hierarchieebene ein div-Element:

const treeWalker = (matrixNode: DataViewMatrixNode, index: number, levels: DataViewHierarchyLevel[], div: HTMLDivElement)  => {
    // generate selectionID for each node of matrix
    const selectionID: ISelectionID = this.host.createSelectionIdBuilder()
        .withMatrixNode(matrixNode, levels)
        .createSelectionId();
    // ...
    if (matrixNode.children) {
        // create div element for level
        const childDiv = document.createElement("div");
        // add to current div
        div.appendChild(childDiv);
        // create paragraph element to display next
        const p = document.createElement("p");
        // display level name on paragraph element
        const level = levels[matrixNode.level];
        p.innerText = level.sources[level.sources.length - 1].displayName;
        // add paragraph element to created child div
        childDiv.appendChild(p);
        // traversing child nodes
        matrixNode.children.forEach((node, index) => treeWalker(node, index, levels, childDiv));
    }
}

Erstellen Sie buttons-Elemente, um mit dem Visual zu interagieren und ein Kontextmenü für Matrixdatenpunkte anzuzeigen:

const treeWalker = (matrixNode: DataViewMatrixNode, index: number, levels: DataViewHierarchyLevel[], div: HTMLDivElement)  => {
    // generate selectionID for each node of matrix
    const selectionID: ISelectionID = this.host.createSelectionIdBuilder()
        .withMatrixNode(matrixNode, levels)
        .createSelectionId();

    // create button element
    let button = document.createElement("button");
    // display node value/name of the button's text
    button.innerText = matrixNode.value.toString();

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

    // display context menu on click
    button.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();
    });

    div.appendChild(button);

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

Löschen Sie div-Elemente, bevor Sie Elemente nochmals rendern:

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

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

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

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

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

Schließlich sollte Ihnen ein Visual mit einem Kontextmenü angezeigt werden:

Die Animation zeigt ein Kontextmenü für das Visual mit Optionen für Drilldown und Drillup.