Partager via


API de filtres d’identités de hiérarchie dans les visuels Power BI

L’API de filtre d’identités de hiérarchie permet aux visuels qui utilisent le mappage DataView de matrice de filtrer les données sur plusieurs champs à la fois en fonction des points de données qui utilisent une structure de hiérarchie.

L’API est utile dans les cas suivants :

  • Filtrage des hiérarchies en fonction des points de données
  • Visuels personnalisés qui utilisent des modèles sémantiques avec un groupe sur des clés

Remarque

L’API de filtre d’identités de hiérarchie est disponible à partir de la version 5.9.0 de l’API

L’interface de filtre est illustrée dans le code suivant :

interface IHierarchyIdentityFilter<IdentityType> extends IFilter {
    target: IHierarchyIdentityFilterTarget;
    hierarchyData: IHierarchyIdentityFilterNode<IdentityType>[];
}
  • $schema : https://powerbi.com/product/schema#hierarchyIdentity (héritée de IFilter)

  • filterType : FilterType.HierarchyIdentity (hérité de IFilter)

  • target : tableau de colonnes pertinentes dans la requête. Actuellement, un seul rôle est pris en charge. Par conséquent, la cible n’est pas requise et doit être vide.

  • hierarchyData : éléments sélectionnés et non sélectionnés dans une arborescence de hiérarchies où chaque IHierarchyIdentityFilterNode<IdentityType> représente une sélection de valeur unique.

type IHierarchyIdentityFilterTarget = IQueryNameTarget[]

interface IQueryNameTarget {
    queryName: string;
}
  • queryName : nom de requête de la colonne source dans la requête. Provient de DataViewMetadataColumn
interface IHierarchyIdentityFilterNode<IdentityType> {
    identity: IdentityType;
    children?: IHierarchyIdentityFilterNode<IdentityType>[];
    operator: HierarchyFilterNodeOperators;
}
  • identity : identité de nœud dans DataView. Le IdentityType doit être CustomVisualOpaqueIdentity

  • children : liste des enfants de nœud pertinents pour la sélection actuelle

  • operator : opérateur pour les objets uniques de l’arborescence. L’opérateur peut être l’une des trois options suivantes :

    type HierarchyFilterNodeOperators = "Selected" | "NotSelected" | "Inherited";
    
    • Selected : la valeur est explicitement sélectionnée.

    • NotSelected : la valeur n’est pas sélectionnée explicitement.

    • Inherited: la sélection de la valeur se fait en fonction de la valeur parente dans la hiérarchie, ou par défaut s’il s’agit de la valeur racine.

Gardez à l’esprit les règles suivantes lors de la définition de votre filtre d’identité de hiérarchie :

  • Prenez les identités depuis DataView.
  • Chaque chemin d’accès identity doit être un chemin valide dans le DataView.
  • Chaque feuille doit présenter un opérateur de Selected ou de NotSelected.
  • Pour comparer les identités, utilisez la fonction ICustomVisualsOpaqueUtils.compareCustomVisualOpaqueIdentities.
  • Les identités peuvent changer les champs suivants (par exemple, en ajoutant ou en supprimant des champs). Power BI affecte les identités mises à jour au filter.hierarchyData existant.

Comment utiliser l’API de filtre d’identité de hiérarchie

Le code suivant est un exemple d’utilisation de l’API de filtre d’identité de hiérarchie dans un visuel personnalisé :

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();

Pour appliquer le filtre, utilisez l’appel d’API applyJsonFilter :

this.host.applyJsonFilter(filter, "general", "filter", action);

Pour restaurer le filtre JSON actif, utilisez la propriété jsonFilters située dans « VisualUpdateOptions » :

export interface VisualUpdateOptions extends extensibility.VisualUpdateOptions {
   //...
   jsonFilters?: IFilter[];
}

Le filtre HierarchyIdnetity est pris en charge uniquement pour les champs hiérarchiquement associés. Par défaut, Power BI ne valide pas si les champs sont hiérarchiquement liés.

Pour activer la validation hiérarchiquement associée, ajoutez la propriété « areHierarchicallyRelated » à la condition de rôle appropriée dans le fichier capabilities.json :

"dataViewMappings": [
    {
         "conditions": [
             {
                  "Rows": {
                      "min": 1,
                      "areHierarchicallyRelated": true <------ NEW ------>
                  },
                  "Value": {
                  "min": 0
                  }
            }
        ],
        ...
    }
]

Les champs sont hiérarchiquement liés si les conditions suivantes sont remplies :

  • Aucun bord de relation inclus n’est de cardinalité Plusieurs-à-plusieurs, ni ConceptualNavigationBehavior.Weak.

  • Tous les champs du filtre existent dans le chemin d’accès.

  • Chaque relation dans le chemin a la même direction ou est bidirectionnelle.

  • La direction de la relation correspond à la cardinalité Une à plusieurs ou est bidirectionnelle.

Exemple de relations de hiérarchie

Par exemple, avec la relation d’entité suivante :

Diagramme de la nature bidirectionnelle du filtre.

  • A, B sont hiérarchiquement liés : true
  • B, C sont hiérarchiquement liés : true
  • A, B, C sont hiérarchiquement liés : true
  • A, C, E sont hiérarchiquement liés : true (A --> E --> C)
  • A, B, E sont hiérarchiquement liés : true (B --> A --> E)
  • A, B, C, E sont hiérarchiquement liés : true (B --> A --> E --> C)
  • A, B, C, D sont hiérarchiquement liés : false (règle non respectée #3)
  • C, D sont hiérarchiquement liés : true
  • B, C, D sont hiérarchiquement liés : false (règle non respectée #3)
  • A, C, D, E sont hiérarchiquement liés : false (règle non respectée #3)

Remarque

  • Lorsque ces validations sont activées et que les champs ne sont pas hiérarchiquement liés, le visuel ne s’affiche pas et un message d’erreur apparaît :

    Capture d’écran du visuel avec échec du chargement des validations activées, car les champs ne sont pas liés hiérarchiquement. Le message d’erreur indique que vous utilisez des champs qui n’ont pas d’ensemble de relations pris en charge.

    Capture d’écran du message d’erreur lorsque les validations sont activées et que les champs ne sont pas hiérarchiquement liés. Le message indique « Impossible d’afficher ce visuel ».

  • Lorsque ces validations sont désactivées et que le visuel de filtre applique un filtre qui contient des nœuds liés à des champs non hiérarchiquement liés, d’autres visuels peuvent ne pas s’afficher correctement lorsque les mesures sont en cours d’utilisation :

    Capture d’écran du visuel avec échec du chargement des validations désactivées, car les champs ne sont pas liés hiérarchiquement. Le message d’erreur indique « Impossible de charger les données pour ce visuel ».

    Capture d’écran du message d’erreur lorsque les validations sont désactivées et que les champs ne sont pas hiérarchiquement liés. Le message indique « Impossible de charger des données pour ce visuel ».

Exemple de code pour la mise à jour de l’arborescence de données de hiérarchie après la nouvelle sélection

Le code suivant montre comment mettre à jour l’arborescence hierarchyData après une sélection :

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();
}

Observations et limitations

  • Ce filtre est pris en charge uniquement pour le mappage de dataView de matrice.

  • Le visuel ne doit contenir qu’un seul rôle de données de regroupement.

  • Un visuel qui utilise le type de filtre d’identités de hiérarchie ne doit appliquer qu’un seul filtre de ce type.