添加向下钻取支持
- 版本 :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;
}
准备示例数据以测试视觉对象:
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 |
并在 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);
});
将数据应用于视觉对象:
在最后的步骤,你将获取带有选择和上下文菜单的视觉对象:
添加矩阵数据视图映射的向下钻取支持
准备示例数据以使用矩阵数据视图映射测试视觉对象:
行 1 | 行 2 | 行 3 | Column1 | Column2 | Column3 | 值 |
---|---|---|---|---|---|---|
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 |
对该视觉对象应用以下数据视图映射:
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"
}
}
}
}
]
}
将数据应用于视觉对象:
导入所需接口以处理矩阵数据视图映射:
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));
}
在最后的步骤,你将获取带有上下文菜单的视觉对象: