添加向下钻取支持

  • 版本 :2023.1(当前版本)

添加向下钻取支持

如何视觉对象具有层次结构,你可以让用户使用 Power BI 向下钻取功能显示其他详细信息。

在此处阅读有关 Power BI 向下钻取功能的详细信息

在视觉对象中启用向下钻取支持

若要在视觉对象中支持向下钻取操作,请向 capabilities.json 添加一个名为“drill-down”的新字段。 该字段有一个名为 roles 的属性,其中包含要对其启用向下钻取操作的 dataRole 的名称。

JSON复制

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

备注

向下钻取 dataRole 必须为 Grouping 类型。 必须将 dataRole 条件中的 max 属性设置为 1。

将角色添加到“向下钻取”字段后,用户可以将多个字段拖到数据角色中。

例如:

JSON复制

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

创建带有向下钻取支持的视觉对象

运行

cmd复制

pbiviz new testDrillDown -t default

以创建默认示例视觉对象。 并将上述 capabilities.json 示例应用于新创建的视觉对象。

创建 div 容器的属性以保存视觉对象的 HTML 元素:

TypeScript复制

"use strict";import "core-js/stable";import "./../style/visual.less";// importsexport class Visual implements IVisual {    
// visual properties
// ...
private div: HTMLDivElement; //

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

更新视觉对象的构造函数:

TypeScript复制

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"); //
this.target.appendChild(new_p);
}
}
}

更新视觉对象的 update 方法,以创建 button

TypeScript复制

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

} // ...

.\style\visual.less 中应用简单样式:

less复制

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

准备示例数据以测试视觉对象:

H1H2H3VALUES
AA1A111
AA1A122
AA2A213
AA2A224
AA3A315
AA3A326
BB1B117
BB1B128
BB2B219
BB2B2210
BB3B3111
BB3B3212

并在 Power BI Desktop 中创建层次结构:

屏幕截图展示了 Power BI Desktop,其中在关联菜单中选择了“新建层次结构”。

将所有类别列(H1、H2、H3)包括到新层次结构中:

屏幕截图展示了可以添加到新层次结构中的类别列。

完成这些步骤后,将获取以下视觉对象:

具有按钮的开发视觉对象

将上下文菜单添加到视觉对象元素

在此步骤中,你将向视觉对象上的按钮添加上下文菜单:

视觉对象中的上下文菜单

若要创建上下文菜单,请保存视觉对象属性中的 host 对象,并使用 Power BI 视觉对象 API 调用创建选择管理器的 createSelectionManager 方法以显示上下文菜单。

TypeScript复制

"use strict";import "core-js/stable";import "./../style/visual.less";// default importsimport 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; //
private selectionManager: ISelectionManager; //

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

forEach 函数回调的正文更改为:

TypeScript复制

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

将数据应用于视觉对象:

屏幕截图展示了层次结构,其中突出显示了“H2”。

在最后的步骤,你将获取带有选择和上下文菜单的视觉对象:

动画展示了从视觉对象的关联菜单中选择“向下钻取”和“向上钻取”。

添加矩阵数据视图映射的向下钻取支持

准备示例数据以使用矩阵数据视图映射测试视觉对象:

行 1行 2行 3Column1Column2Column3
R1R11R111C1C11C1111
R1R11R112C1C11C1122
R1R11R113C1C11C1133
R1R12R121C1C12C1214
R1R12R122C1C12C1225
R1R12R123C1C12C1236
R1R13R131C1C13C1317
R1R13R132C1C13C1328
R1R13R133C1C13C1339
R2R21R211C2C21C21110
R2R21R212C2C21C21211
R2R21R213C2C21C21312
R2R22R221C2C22C22113
R2R22R222C2C22C22214
R2R22R223C2C22C22316
R2R23R231C2C23C23117
R2R23R232C2C23C23218
R2R23R233C2C23C23319

对该视觉对象应用以下数据视图映射:

JSON复制

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

将数据应用于视觉对象:

屏幕截图展示了 MatrixHierarchy,其中选择了列和行层次结构及其成员。

导入所需接口以处理矩阵数据视图映射:

TypeScript复制

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

为行和列元素的两个 div 创建两个属性:

TypeScript复制

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

在呈现元素之前检查数据并显示层次结构的当前级别:

TypeScript复制

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

创建用于遍历层次结构的函数 treeWalker

TypeScript复制

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

为数据点生成选择项。

TypeScript复制

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

为每个层次结构级别创建 div

TypeScript复制

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

创建 buttons 以与视觉对象进行交互并显示用于矩阵数据点的上下文菜单:

TypeScript复制

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

在再次呈现元素之前清除 div 元素:

TypeScript复制

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

在最后的步骤,你将获取带有上下文菜单的视觉对象:

动画展示了视觉对象的关联菜单,其中包含“向下钻取”或“向上钻取”选项。