API иерархических удостоверений фильтрует в визуальных элементах Power BI
API фильтра удостоверений иерархии позволяет визуальным элементам, используюющим сопоставление Matrix DataView, фильтровать данные по нескольким полям за раз на основе точек данных, использующих структуру иерархии.
Этот API полезен в следующих сценариях:
- Фильтрация иерархий на основе точек данных
- Пользовательские визуальные элементы, использующие семантические модели с группой по ключам
Примечание.
API фильтра удостоверений иерархии доступен из API версии 5.9.0
Интерфейс фильтра показан в следующем коде:
interface IHierarchyIdentityFilter<IdentityType> extends IFilter {
target: IHierarchyIdentityFilterTarget;
hierarchyData: IHierarchyIdentityFilterNode<IdentityType>[];
}
$schema:
https://powerbi.com/product/schema#hierarchyIdentity
(наследуется от IFilter)filterType: FilterType.HierarchyIdentity (наследуется от IFilter)
целевой объект: массив соответствующих столбцов в запросе. В настоящее время поддерживается только одна роль; поэтому целевой объект не является обязательным и должен быть пустым.
hierarchyData: выбранные и неизбираемые элементы в дереве иерархии, где каждый
IHierarchyIdentityFilterNode<IdentityType>
представляет один выбор значения.
type IHierarchyIdentityFilterTarget = IQueryNameTarget[]
interface IQueryNameTarget {
queryName: string;
}
- queryName: имя запроса исходного столбца в запросе. Это происходит от
DataViewMetadataColumn
interface IHierarchyIdentityFilterNode<IdentityType> {
identity: IdentityType;
children?: IHierarchyIdentityFilterNode<IdentityType>[];
operator: HierarchyFilterNodeOperators;
}
удостоверение: удостоверение узла в DataView. Должно
IdentityType
бытьCustomVisualOpaqueIdentity
дочерние элементы: список дочерних элементов узла, относящихся к текущему выбору
оператор: оператор для отдельных объектов в дереве. Оператор может быть одним из следующих трех вариантов:
type HierarchyFilterNodeOperators = "Selected" | "NotSelected" | "Inherited";
Выбрано: значение явно выбрано.
NotSelected: значение явно не выбрано.
Наследуется: выбор значения соответствует родительскому значению в иерархии или по умолчанию, если это корневое значение.
При определении фильтра удостоверений иерархии следует учитывать следующие правила:
- Возьмите удостоверения из DataView.
- Каждый путь удостоверения должен быть допустимым путем в DataView.
- Каждый лист должен иметь оператор Selected или NotSelected.
- Чтобы сравнить удостоверения, используйте функцию
ICustomVisualsOpaqueUtils.compareCustomVisualOpaqueIdentities
. - Удостоверения могут изменяться после изменений полей (например, добавление или удаление полей). Power BI назначает обновленные удостоверения существующим filter.hierarchyData.
Использование API фильтра удостоверений иерархии
Следующий код является примером использования API фильтрации удостоверений иерархии в пользовательском визуальном элементе:
import { IHierarchyIdentityFilterTarget, IHierarchyIdentityFilterNode, HierarchyIdentityFilter } from "powerbi-models"
const target: IHierarchyIdentityFilterTarget = [];
const hierarchyData: IHierarchyIdentityFilterNode<CustomVisualOpaqueIdentity>[] = [
{
identity: {...},
operator: "Selected",
children: [
{
identity: {...},
operator: "NotSelected"
}
]
},
{
identity: {...},
operator: "Inherited",
children: [
{
identity: {...},
operator: "Selected"
}
]
}
];
const filter = new HierarchyIdentityFilter(target, hierarchyData).toJSON();
Чтобы применить фильтр, используйте applyJsonFilter
вызов API:
this.host.applyJsonFilter(filter, "general", "filter", action);
Чтобы восстановить активный фильтр JSON, используйте свойство, найденное jsonFilters
в visualUpdateOptions:
export interface VisualUpdateOptions extends extensibility.VisualUpdateOptions {
//...
jsonFilters?: IFilter[];
}
Проверка связанных с иерархией полей (необязательно)
Фильтр HierarchyIdnetity
поддерживается только для иерархически связанных полей. По умолчанию Power BI не проверяет, связаны ли поля иерархически.
Чтобы активировать иерархическую проверку, добавьте свойство areHierarchicallyRelated в соответствующее условие роли в файле capabilities.json:
"dataViewMappings": [
{
"conditions": [
{
"Rows": {
"min": 1,
"areHierarchicallyRelated": true <------ NEW ------>
},
"Value": {
"min": 0
}
}
],
...
}
]
Поля иерархически связаны, если выполняются следующие условия:
Нет границы связи, которые не включаются во многих карта inality, или
ConceptualNavigationBehavior.Weak
.Все поля в фильтре существуют в пути.
Каждая связь в пути имеет одинаковое направление или двунаправленное.
Направление связи соответствует карта inality для одного или нескольких или двунаправленных.
Пример связей иерархии
Например, учитывая следующую связь сущности:
- A, B иерархически связаны: true
- B, C иерархически связаны: true
- A, B, C иерархически связаны: true
- A, C, E иерархически связаны: true (A --> E --> C)
- A, B, E иерархически связаны: true (B --> A --> E)
- A, B, C, E иерархически связаны: true (B --> A --> E --> C)
- A, B, C, D иерархически связаны: false (нарушенное правило #3)
- C, D иерархически связаны: true
- B, C, D иерархически связаны: false (нарушенное правило #3)
- A, C, D, E иерархически связаны: false (нарушенное правило #3)
Примечание.
Если эти проверки включены, и поля не связаны иерархически, визуальный элемент не будет отображаться, и появится сообщение об ошибке:
Если эти проверки отключены, и визуальный элемент фильтра применяет фильтр, содержащий узлы, связанные с не иерархически связанными полями, другие визуальные элементы могут неправильно отображаться при использовании мер:
Пример кода для обновления дерева данных иерархии после нового выбора
В следующем коде показано, как обновить hierarchyData
дерево после создания выбора:
type CompareIdentitiesFunc = (id1: CustomVisualOpaqueIdentity, id2: CustomVisualOpaqueIdentity) => boolean;
/**
* Updates the filter tree following a new node selection.
* Prunes irrelevant branches after node insertion/removal if necessary.
* @param path Identities path to the selected node.
* @param treeNodes Array of IHierarchyIdentityFilterNode representing a valid filter tree.
* @param compareIdentities Compare function for CustomVisualOpaqueIdentity to determine equality. Pass the ICustomVisualsOpaqueUtils.compareCustomVisualOpaqueIdentities function.
* @returns A valid filter tree after the update
*/
function updateFilterTreeOnNodeSelection(
path: CustomVisualOpaqueIdentity[],
treeNodes: IHierarchyIdentityFilterNode<CustomVisualOpaqueIdentity>[],
compareIdentities: CompareIdentitiesFunc
): IHierarchyIdentityFilterNode<CustomVisualOpaqueIdentity>[] {
if (!path) return treeNodes;
const root: IHierarchyIdentityFilterNode<CustomVisualOpaqueIdentity> = {
identity: null,
children: treeNodes || [],
operator: 'Inherited',
};
let currentNodesLevel = root.children;
let isClosestSelectedParentSelected = root.operator === 'Selected';
let parents: { node: IHierarchyIdentityFilterNode<CustomVisualOpaqueIdentity>, index: number }[] = [{ node: root, index: -1 }];
let shouldFixTree = false;
path.forEach((identity, level) => {
const index = currentNodesLevel.findIndex((node) => compareIdentities(node.identity, identity));
const isLastNodeInPath = level === path.length - 1
if (index === -1) {
const newNode: IHierarchyIdentityFilterNode<CustomVisualOpaqueIdentity> = {
identity,
children: [],
operator: isLastNodeInPath ? (isClosestSelectedParentSelected ? 'NotSelected' : 'Selected') : 'Inherited',
};
currentNodesLevel.push(newNode);
currentNodesLevel = newNode.children;
if (newNode.operator !== 'Inherited') {
isClosestSelectedParentSelected = newNode.operator === 'Selected';
}
} else {
const currentNode = currentNodesLevel[index];
if (isLastNodeInPath) {
const partial = currentNode.children && currentNode.children.length;
if (partial) {
/**
* The selected node has subtree.
* Therefore, selecting this node should lead to one of the following scenarios:
* 1. The node should have Selected operator and its subtree should be pruned.
* 2. The node and its subtree should be pruned form the tree and the tree should be fixed.
*/
// The subtree should be always pruned.
currentNode.children = [];
if (currentNode.operator === 'NotSelected' || (currentNode.operator === 'Inherited' && isClosestSelectedParentSelected )) {
/**
* 1. The selected node has NotSelected operator.
* 2. The selected node has Inherited operator, and its parent has Slected operator.
* In both cases the node should be pruned from the tree and the tree shoud be fixed.
*/
currentNode.operator = 'Inherited'; // to ensure it will be pruned
parents.push({ node: currentNode, index });
shouldFixTree = true;
} else {
/**
* 1. The selected node has Selected operator.
* 2. The selected node has Inherited operator, but its parent doesn't have Selected operator.
* In both cases the node should stay with Selected operator pruned from the tree and the tree should be fixed.
* Note that, node with Selected oprator and parent with Selector operator is not valid state.
*/
currentNode.operator = 'Selected';
}
} else {
// Leaf node. The node should be pruned from the tree and the tree should be fixed.
currentNode.operator = 'Inherited'; // to ensure it will be pruned
parents.push({ node: currentNode, index });
shouldFixTree = true;
}
} else {
// If it's not the last noded in path we just continue traversing the tree
currentNode.children = currentNode.children || [];
currentNodesLevel = currentNode.children
if (currentNode.operator !== 'Inherited') {
isClosestSelectedParentSelected = currentNode.operator === 'Selected';
// We only care about the closet parent with Selected/NotSelected operator and its children
parents = [];
}
parents.push({ node: currentNode, index });
}
}
});
// Prune brnaches with Inherited leaf
if (shouldFixTree) {
for (let i = parents.length - 1; i >= 1; i--) {
// Normalize to empty array
parents[i].node.children = parents[i].node.children || [];
if (!parents[i].node.children.length && (parents[i].node.operator === 'Inherited')) {
// Remove the node from its parent children array
removeElement(parents[i - 1].node.children, parents[i].index);
} else {
// Node has children or Selected/NotSelected operator
break;
}
}
}
return root.children;
}
/**
* Removes an element from the array without preserving order.
* @param arr - The array from which to remove the element.
* @param index - The index of the element to be removed.
*/
function removeElement(arr: any[], index: number): void {
if (!arr || !arr.length || index < 0 || index >= arr.length) return;
arr[index] = arr[arr.length - 1];
arr.pop();
}
Рекомендации и ограничения
Этот фильтр поддерживается только для сопоставления матрицы dataView.
Визуальный элемент должен содержать только одну роль группированияданных.
Визуальный элемент, использующий тип фильтра удостоверений иерархии, должен применять только один фильтр этого типа.