通过 Power BI 视觉对象选择将交互性添加到视觉对象

Power BI 提供了两种与视觉对象交互的方式 - 选择和筛选。 下面的示例演示如何从一个视觉对象中选择一个项,并就新的选择状态通知报表中的其他视觉对象。

接口对应于一个 Selection 对象:

export interface ISelectionId {
    equals(other: ISelectionId): boolean;
    includes(other: ISelectionId, ignoreHighlight?: boolean): boolean;
    getKey(): string;
    getSelector(): Selector;
    getSelectorsByColumn(): SelectorsByColumn;
    hasIdentity(): boolean;
}

使用选择管理器选择数据点

视觉宿主对象提供创建选择管理器实例的方法。 选择管理器为以下每个操作提供相应方法:

  • Select
  • 清除选择
  • 显示上下文菜单
  • 存储当前所选内容
  • 检查选择状态

创建选择管理器的实例

要使用选择管理器,请创建选择管理器实例。 通常,视觉对象会在可视对象的 constructor 部分创建选择管理器实例。

export class Visual implements IVisual {
    private target: HTMLElement;
    private host: IVisualHost;
    private selectionManager: ISelectionManager;
    // ...
    constructor(options: VisualConstructorOptions) {
        this.host = options.host;
        // ...
        this.selectionManager = this.host.createSelectionManager();
    }
    // ...
}

创建选择生成器的实例

创建选择管理器实例时,需要为视觉对象的每个数据点创建 selections。 视觉宿主对象的 createSelectionIdBuilder 方法为每个数据点生成一个选择。 此方法会返回带 powerbi.visuals.ISelectionIdBuilder 接口的对象实例:

export interface ISelectionIdBuilder {
    withCategory(categoryColumn: DataViewCategoryColumn, index: number): this;
    withSeries(seriesColumn: DataViewValueColumns, valueColumn: DataViewValueColumn | DataViewValueColumnGroup): this;
    withMeasure(measureId: string): this;
    withMatrixNode(matrixNode: DataViewMatrixNode, levels: DataViewHierarchyLevel[]): this;
    withTable(table: DataViewTable, rowIndex: number): this;
    createSelectionId(): ISelectionId;
}

此对象具有可为不同类型的数据视图映射创建 selections 的对应方法。

注意

在 Power BI 视觉对象的 API 2.5.0 上引入了 withTablewithMatrixNode 方法。 如果需要将选择用于表或矩阵数据视图映射,请将 API 版本更新为 2.5.0 或更高版本。

为分类数据视图映射创建选择

让我们回顾一下选择如何表示示例语义模型的分类数据视图映射:

制造商 类型
Chrysler 国产汽车 28883
Chrysler 国产卡车 117131
Chrysler 进口汽车 0
Chrysler 进口卡车 6362
Ford 国产汽车 50032
Ford 国产卡车 122446
Ford 进口汽车 0
Ford 进口卡车 0
GM 国产汽车 65426
GM 国产卡车 138122
GM 进口汽车 197
GM 进口卡车 0
Honda 国产汽车 51450
Honda 国产卡车 46115
Honda 进口汽车 2932
Honda 进口卡车 0
Nissan 国产汽车 51476
Nissan 国产卡车 47343
Nissan 进口汽车 5485
Nissan 进口卡车 1430
Toyota 国产汽车 55643
Toyota 国产卡车 61227
Toyota 进口汽车 20799
Toyota 进口卡车 23614

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

{
    "dataRoles": [
        {
            "displayName": "Columns",
            "name": "columns",
            "kind": "Grouping"
        },
        {
            "displayName": "Rows",
            "name": "rows",
            "kind": "Grouping"
        },
        {
            "displayName": "Values",
            "name": "values",
            "kind": "Measure"
        }
    ],
    "dataViewMappings": [
        {
            "categorical": {
                "categories": {
                    "for": {
                        "in": "columns"
                    }
                },
                "values": {
                    "group": {
                        "by": "rows",
                        "select": [
                            {
                                "for": {
                                    "in": "values"
                                }
                            }
                        ]
                    }
                }
            }
        }
    ]
}

在上一示例中,ManufacturercolumnsTyperows。 序列通过按 rows (Type) 对值进行分组创建。

视觉对象应可以通过 ManufacturerType 来切分数据。

例如,如果用户按 Manufacturer 选择 Chrysler,其他视觉对象应显示以下数据:

制造商 类型
Chrysler 国产汽车 28883
Chrysler 国产卡车 117131
Chrysler 进口汽车 0
Chrysler 进口卡车 6362

当用户按 Type 选择 Import Car 时(按序列选择数据),其他视觉对象应显示以下数据:

制造商 类型
Chrysler 进口汽车 0
Ford 进口汽车 0
GM 进口汽车 197
Honda 进口汽车 2932
Nissan 进口汽车 5485
Toyota 进口汽车 20799

Screenshot that shows the visual with selections.

若要显示切片数据,请填充视觉对象的数据篮,如下所示:

Screenshot that shows visual's data baskets.

在上例中,Manufacturer 是类别(列),Type 是序列(行),Sales 是序列的 Values

注意

Values 是显示序列所必需的,因为根据数据视图映射,ValuesRows 数据分组。

为类别创建选择

// categories
const categories = dataView.categorical.categories;

// create label for 'Manufacturer' column
const p = document.createElement("p") as HTMLParagraphElement;
p.innerText = categories[0].source.displayName.toString();
this.target.appendChild(p);

// get count of category elements
const categoriesCount = categories[0].values.length;

// iterate all categories to generate selection and create button elements to use selections
for (let categoryIndex = 0; categoryIndex < categoriesCount; categoryIndex++) {
    const categoryValue: powerbi.PrimitiveValue = categories[0].values[categoryIndex];

    const categorySelectionId = this.host.createSelectionIdBuilder()
        .withCategory(categories[0], categoryIndex) // we have only one category (only one `Manufacturer` column)
        .createSelectionId();
    this.dataPoints.push({
        value: categoryValue,
        selection: categorySelectionId
    });
    console.log(categorySelectionId);

    // create button element to apply selection on click
    const button = document.createElement("button") as HTMLButtonElement;
    button.value = categoryValue.toString();
    button.innerText = categoryValue.toString();
    button.addEventListener("click", () => {
        // handle click event to apply correspond selection
        this.selectionManager.select(categorySelectionId);
    });
    this.target.appendChild(button);
}

在以上示例代码中,我们将遍历所有类别。 每次迭代时,我们都会通过调用选择生成器的 withCategory 方法调用 createSelectionIdBuilder 来为每个类别创建下一个选择。 createSelectionId 方法用作返回生成的 selection 对象的最终方法。

withCategory 方法中,我们将传递 category 列,在示例中为 Manufacturer,也是类别元素的索引。

为序列创建选择

// get groupped values for series
const series: powerbi.DataViewValueColumnGroup[] = dataView.categorical.values.grouped();

// create label for 'Type' column
const p2 = document.createElement("p") as HTMLParagraphElement;
p2.innerText = dataView.categorical.values.source.displayName;
this.target.appendChild(p2);

// iterate all series to generate selection and create button elements to use selections
series.forEach( (ser: powerbi.DataViewValueColumnGroup) => {
    // create selection id for series
    const seriesSelectionId = this.host.createSelectionIdBuilder()
        .withSeries(dataView.categorical.values, ser)
        .createSelectionId();

    this.dataPoints.push({
        value: ser.name,
        selection: seriesSelectionId
    });

    // create button element to apply selection on click
    const button = document.createElement("button") as HTMLButtonElement;
    button.value =ser.name.toString();
    button.innerText = ser.name.toString();
    button.addEventListener("click", () => {
        // handle click event to apply correspond selection
        this.selectionManager.select(seriesSelectionId);
    });
    this.target.appendChild(button);
});

为表数据视图映射创建选择

以下示例显示表数据视图映射:

{
    "dataRoles": [
        {
            "displayName": "Values",
            "name": "values",
            "kind": "GroupingOrMeasure"
        }
    ],
    "dataViewMappings": [
        {
            "table": {
                "rows": {
                    "for": {
                        "in": "values"
                    }
                }
            }
        }
    ]
}

若要为表数据视图映射的每一行创建选择,请调用选择生成器的 withTable 方法。

public update(options: VisualUpdateOptions) {
    const dataView = options.dataViews[0];
    dataView.table.rows.forEach((row: DataViewTableRow, rowIndex: number) => {
        this.target.appendChild(rowDiv);
        const selection: ISelectionId = this.host.createSelectionIdBuilder()
            .withTable(dataView.table, rowIndex)
            .createSelectionId();
    }
}

视觉对象代码将循环访问表中的行,每行都会调用 withTable 表方法。 withTable 方法的参数是表行的 table 对象和索引。

为矩阵数据视图映射创建选择

public update(options: VisualUpdateOptions) {
    const host = this.host;
    const rowLevels: powerbi.DataViewHierarchyLevel[] = dataView.matrix.rows.levels;
    const columnLevels: powerbi.DataViewHierarchyLevel[] = dataView.matrix.rows.levels;

    // iterate rows hierarchy
    nodeWalker(dataView.matrix.rows.root, rowLevels);
    // iterate columns hierarchy
    nodeWalker(dataView.matrix.columns.root, columnLevels);

    function nodeWalker(node: powerbi.DataViewMatrixNode, levels: powerbi.DataViewHierarchyLevel[]) {
        const nodeSelection = host.createSelectionIdBuilder().withMatrixNode(node, levels);

        if (node.children && node.children.length) {
            node.children.forEach(child => {
                nodeWalker(child, levels);
            });
        }
    }
}

在示例中,nodeWalker 以递归方式调用每个节点和子节点。

nodeWalker 会在每次调用时创建 nodeSelection 对象。 每个 nodeSelection 表示相应节点的一个 selection

选择数据点以切分其他视觉对象

本示例为按钮元素创建了一个单击处理程序。 该处理程序调用选择管理器的 select 方法,并传递选择对象。

button.addEventListener("click", () => {
    // handle click event to apply correspond selection
    this.selectionManager.select(categorySelectionId);
});

select 方法的接口:

interface ISelectionManager {
    // ...
    select(selectionId: ISelectionId | ISelectionId[], multiSelect?: boolean): IPromise<ISelectionId[]>;
    // ...
}

select 方法可以接受选择的数组。 这样,视觉对象可以同时选择多个数据点。 第二个参数 multiSelect 负责多选。 如果 multiSelect 为 true,则 Power BI 在应用当前选择时不会清除以前的选择状态。 如果值为 false,则覆盖上一个选择。

使用 multiSelect 的典型示例是处理单击事件上的 Ctrl 按钮状态。 按下 Ctrl 按钮时,可以选择多个对象。

button.addEventListener("click", (mouseEvent) => {
    const multiSelect = (mouseEvent as MouseEvent).ctrlKey;
    this.selectionManager.select(seriesSelectionId, multiSelect);
});