Die Hierarchy Identity Filter-API in Power BI-Visuals
Die Hierarchy Identity Filter-API ermöglicht es Visuals, die Matrix-DataView-Zuordnungen verwenden, Daten nach mehreren Feldern gleichzeitig basierend auf Datenpunkten zu filtern, die eine Hierarchiestruktur verwenden.
Diese API ist für die folgenden Szenarien nützlich:
- Filtern von Hierarchien basierend auf Datenpunkten
- Für benutzerdefinierte Visuals, die semantische Modelle mit Gruppierung nach Schlüsseln verwenden
Hinweis
Die Hierarchy Identity Filter-API ist ab API-Version 5.9.0 verfügbar.
Die Filterschnittstelle wird im folgenden Code gezeigt:
interface IHierarchyIdentityFilter<IdentityType> extends IFilter {
target: IHierarchyIdentityFilterTarget;
hierarchyData: IHierarchyIdentityFilterNode<IdentityType>[];
}
$schema:
https://powerbi.com/product/schema#hierarchyIdentity
(von IFilter geerbt)filterType: FilterType.HierarchyIdentity (von IFilter geerbt)
target: Array relevanter Spalten in der Abfrage. Derzeit wird nur eine einzelne Rolle unterstützt. Daher ist das Ziel nicht erforderlich und sollte leer sein.
hierarchyData: Die ausgewählten und nicht ausgewählten Elemente in einer Hierarchiestruktur, wobei jeder
IHierarchyIdentityFilterNode<IdentityType>
eine einzelne Wertauswahl darstellt.
type IHierarchyIdentityFilterTarget = IQueryNameTarget[]
interface IQueryNameTarget {
queryName: string;
}
- queryName: Abfragename der Quellspalte in der Abfrage. Stammt aus
DataViewMetadataColumn
.
interface IHierarchyIdentityFilterNode<IdentityType> {
identity: IdentityType;
children?: IHierarchyIdentityFilterNode<IdentityType>[];
operator: HierarchyFilterNodeOperators;
}
identity: Die Knotenidentität in DataView.
IdentityType
sollteCustomVisualOpaqueIdentity
sein.children: Liste der untergeordneten Knotenelemente, die für die aktuelle Auswahl relevant sind.
operator: Der Operator für einzelne Objekte in der Struktur. Der Operator kann eine der folgenden drei Optionen sein:
type HierarchyFilterNodeOperators = "Selected" | "NotSelected" | "Inherited";
Selected: Der Wert ist explizit ausgewählt.
NotSelected: Der Wert ist explizit nicht ausgewählt.
Inherited: Die Wertauswahl entspricht dem übergeordneten Wert in der Hierarchie oder dem Standardwert, wenn es sich um den Stammwert handelt.
Beachten Sie beim Definieren des Hierarchieidentitätsfilters die folgenden Regeln:
- Übernehmen Sie die Identitäten aus DataView.
- Jeder identity-Pfad sollte ein gültiger Pfad in DataView sein.
- Jedes Blatt sollte über einen Operator von Selected oder NotSelected verfügen.
- Verwenden Sie zum Vergleichen von Identitäten die
ICustomVisualsOpaqueUtils.compareCustomVisualOpaqueIdentities
-Funktion. - Die Identitäten können sich nach Feldänderungen ändern (z. B. Hinzufügen oder Entfernen von Feldern). Power BI weist die aktualisierten Identitäten filter.hierarchyData zu.
So verwenden Sie die Hierarchy Identity Filter-API
Der folgende Code ist ein Beispiel für die Verwendung der Hierarchy Identity Filter-API in einem benutzerdefinierten visuellen Element:
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();
Um den Filter anzuwenden, verwenden Sie den API-Aufruf applyJsonFilter
:
this.host.applyJsonFilter(filter, "general", "filter", action);
Um den aktiven JSON-Filter wiederherzustellen, verwenden Sie die jsonFilters
-Eigenschaft in „VisualUpdateOptions“:
export interface VisualUpdateOptions extends extensibility.VisualUpdateOptions {
//...
jsonFilters?: IFilter[];
}
Überprüfung hierarchiebezogener Felder (optional)
Der HierarchyIdnetity
-Filter wird nur für hierarchisch verwandte Felder unterstützt. Power BI überprüft standardmäßig nicht, ob die Felder hierarchisch verknüpft sind.
Um die hierarchiebezogene Überprüfung zu aktivieren, fügen Sie die Eigenschaft „areHierarchicallyRelated“ zur relevanten Rollenbedingung in der Datei „capabilities.json“ hinzu:
"dataViewMappings": [
{
"conditions": [
{
"Rows": {
"min": 1,
"areHierarchicallyRelated": true <------ NEW ------>
},
"Value": {
"min": 0
}
}
],
...
}
]
Felder sind hierarchisch verknüpft, wenn die folgenden Bedingungen erfüllt sind:
Keine eingeschlossene Beziehungskante hat eine m:n-Kardinalität oder
ConceptualNavigationBehavior.Weak
.Alle Felder im Filter sind im Pfad vorhanden.
Jede Beziehung im Pfad weist dieselbe Richtung auf oder ist bidirektional.
Die Beziehungsrichtung entspricht der Kardinalität für 1:n- oder bidirektionale Beziehungen.
Beispiel für Hierarchiebeziehungen
Betrachten Sie als Beispiel die folgende Entitätsbeziehung:
- A, B haben eine hierarchische Beziehung: wahr
- B, C haben eine hierarchische Beziehung: wahr
- A, B, C haben eine hierarchische Beziehung: wahr
- A, C, E haben eine hierarchische Beziehung: wahr (A --> E --> C)
- A, B, E haben eine hierarchische Beziehung: wahr (B --> A --> E)
- A, B, C, E haben eine hierarchische Beziehung: wahr (B --> A --> E --> C)
- A, B, C, D haben eine hierarchische Beziehung: falsch (Verstoß gegen Regel 3)
- C, D haben eine hierarchische Beziehung: wahr
- B, C, D haben eine hierarchische Beziehung: falsch (Verstoß gegen Regel 3)
- A, C, D, E haben eine hierarchische Beziehung: falsch (Verstoß gegen Regel 3)
Hinweis
Wenn diese Überprüfungen aktiviert sind und die Felder nicht hierarchisch verknüpft sind, wird das visuelle Element nicht gerendert, und es wird eine Fehlermeldung angezeigt:
Wenn diese Überprüfungen deaktiviert sind und das Filtervisual einen Filter anwendet, der Knoten enthält, die mit nicht hierarchischen Feldern zusammenhängen, werden andere visuelle Elemente möglicherweise nicht ordnungsgemäß gerendert, wenn Measures verwendet werden:
Codebeispiel zum Aktualisieren der Hierarchiedatenstruktur nach der neuen Auswahl
Der folgende Code zeigt, wie die hierarchyData
-Struktur nach einer neuen Auswahl aktualisiert wird:
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();
}