突出显示 Power BI 视觉对象中的数据点

  • 版本 :2023.1(当前版本)

突出显示 Power BI 视觉对象中的数据点

本文介绍了如何在 Power BI 视觉对象中突出显示数据。

默认情况下,只要选择了某个元素,dataView 对象中的 values 数组就会被筛选为所选的值。 这项筛选操作将导致页面上的所有其他视觉对象仅显示所选数据。

如果将 capabilities.json 中的 supportsHighlight 属性设置为 true,则会获得未经筛选的完整 values 数组和 highlights 数组。 highlights 数组的长度与 values 数组的长度相同,并且任何未选定的值都将设置为 null。 启用此属性后,视觉对象会将 values 数组与 highlights 数组进行比较来突出显示相应的数据。

  • 无突出显示支持

  • 突出显示支持

屏幕截图显示默认的“dataview”行为,未突出显示。

在示例中,你将注意到:

  • 如果无突出显示支持,所选项将是 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);

});

创建 divp 元素,用于显示和直观呈现视觉对象 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行 3Column1Column2Column3
R1R11R111C1C11C1111
R1R11R112C1C11C1122
R1R11R113C1C11C1133
R1R12R121C1C12C1214
R1R12R122C1C12C1225
R1R12R123C1C12C1236
R1R13R131C1C13C1317
R1R13R132C1C13C1328
R1R13R133C1C13C1339
R2R21R211C2C21C21110
R2R21R212C2C21C21211
R2R21R213C2C21C21312
R2R22R221C2C22C22113
R2R22R222C2C22C22214
R2R22R223C2C22C22316
R2R23R231C2C23C23117
R2R23R232C2C23C23218
R2R23R233C2C23C23319

创建默认视觉对象项目,并应用 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 的视觉对象

带有矩阵数据视图映射和突出显示的视觉对象