突出显示 Power BI 视觉对象中的数据点
- 版本 :2023.1(当前版本)
突出显示 Power BI 视觉对象中的数据点
本文介绍了如何在 Power BI 视觉对象中突出显示数据。
默认情况下,只要选择了某个元素,dataView
对象中的 values
数组就会被筛选为所选的值。 这项筛选操作将导致页面上的所有其他视觉对象仅显示所选数据。
如果将 capabilities.json
中的 supportsHighlight
属性设置为 true
,则会获得未经筛选的完整 values
数组和 highlights
数组。 highlights
数组的长度与 values 数组的长度相同,并且任何未选定的值都将设置为 null
。 启用此属性后,视觉对象会将 values
数组与 highlights
数组进行比较来突出显示相应的数据。
无突出显示支持
突出显示支持
在示例中,你将注意到:
如果无突出显示支持,所选项将是
values
数组中唯一的值,也是数据视图中唯一显示的条形。如果有突出显示支持,所有值都将在
values
数组中。highlights
数组包含未突出显示元素的null
值。 所有条形都显示在数据视图中,突出显示的条形采用不同的颜色。
还可以选择多项和采用部分突出显示。 突出显示的值将显示在数据视图中。
备注
表数据视图映射不支持突出显示功能。
用分类数据视图映射功能突出显示数据点
对于具有分类数据视图映射的视觉对象,请将 "supportsHighlight": true
添加到 capabilities.json
文件中。 例如:
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}
删除不必要的代码后的默认视觉对象源代码如下所示:
TypeScript复制
"use strict";// ... default imports listimport {
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 的数据:
TypeScript复制
import DataViewCategorical = powerbi.DataViewCategorical;
import DataViewCategoryColumn = powerbi.DataViewCategoryColumn;
import PrimitiveValue = powerbi.PrimitiveValue;import DataViewValueColumn = powerbi.DataViewValueColumn;
为以下类别值创建根 div
元素:
TypeScript复制
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 元素的内容:
TypeScript复制
// ...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
对象获取类别和度量值:
TypeScript复制
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
数组并获取相应的值和突出显示的内容:
TypeScript复制
// ...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 中的数据视图值:
TypeScript复制
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 元素的颜色:
css复制
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;
}
在结果中,应显示为视觉对象的以下视图。
用矩阵数据视图映射突出显示数据点
对于具有矩阵数据视图映射的视觉对象,请将 "supportsHighlight": true
添加到 capabilities.json
文件中。 例如:
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 | 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 |
创建默认视觉对象项目,并应用 capabilities.json
的示例。
删除不必要的代码后的默认视觉对象源代码如下所示:
TypeScript复制
"use strict";// ... default importsimport { 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 的数据:
TypeScript复制
import DataViewMatrix = powerbi.DataViewMatrix;import DataViewMatrixNode = powerbi.DataViewMatrixNode;import DataViewHierarchyLevel = powerbi.DataViewHierarchyLevel;
创建两个用于视觉对象布局的 div
元素:
TypeScript复制
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
方法中的数据,确保视觉对象可获取数据:
TypeScript复制
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
元素的内容:
TypeScript复制
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
函数来遍历矩阵数据结构:
TypeScript复制
public update(options: VisualUpdateOptions) { // ...
const treeWalker = (matrixNode: DataViewMatrixNode, index: number, levels: DataViewHierarchyLevel[], div: HTMLDivElement) => {
} // ...}
其中 matrixNode
是当前节点,levels
是此层次结构级别的元数据列,div
是子 HTML 元素的父元素。
treeWalker
是递归函数,需要为文本创建 div
元素和 p
(用作标头),并为节点的子元素调用函数:
TypeScript复制
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));
}
} // ...}
为矩阵数据视图结构的列和行的根元素调用函数:
TypeScript复制
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,并创建按钮以显示节点:
TypeScript复制
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();
}); // ...
} // ...}
使用突出显示的主要步骤是另创建一个值数组。
如果检查终端节点的对象,则可以发现值数组具有两个属性 - 值和突出显示:
Javascript复制
JSON.stringify(options.dataViews[0].matrix.rows.root.children[0].children[0].children[0], null, " ");
JSON复制
{ "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
数组(如果呈现)的代码:
TypeScript复制
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
的视觉对象