Condividi tramite


API dei filtri di identità gerarchici negli oggetti visivi di Power BI

L'API di filtro dell’Identità gerarchia consente agli oggetti visivi che usano il mapping di Matrice DataView per filtrare i dati su più campi alla volta in base ai punti dati che usano una struttura gerarchica.

Questa API è utile negli scenari seguenti:

  • Filtro delle gerarchie in base ai punti dati
  • Oggetti visivi personalizzati che usano modelli semantici con raggruppamento in chiavi

Nota

L'API filtro identità gerarchica è disponibile nell'API versione 5.9.0

L'interfaccia del filtro è illustrata nel codice seguente:

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

  • filterType: FilterType.HierarchyIdentity (ereditato da IFilter)

  • target: matrice di colonne pertinenti nella query. Attualmente è supportato solo un singolo ruolo; pertanto, la destinazione non è obbligatoria e deve essere vuota.

  • hierarchyData: gli elementi selezionati e non selezionati in un albero della gerarchia in cui ogni IHierarchyIdentityFilterNode<IdentityType> rappresenta una selezione di un singolo valore.

type IHierarchyIdentityFilterTarget = IQueryNameTarget[]

interface IQueryNameTarget {
    queryName: string;
}
  • queryName: nome della query della colonna di origine nella query. Proviene dal DataViewMetadataColumn
interface IHierarchyIdentityFilterNode<IdentityType> {
    identity: IdentityType;
    children?: IHierarchyIdentityFilterNode<IdentityType>[];
    operator: HierarchyFilterNodeOperators;
}
  • identity: identità del nodo in DataView. IdentityType deve essere CustomVisualOpaqueIdentity

  • children: elenco di elementi figlio del nodo rilevanti per la selezione corrente

  • operator: operatore per singoli oggetti nell'albero. L'operatore può essere una delle tre opzioni seguenti:

    type HierarchyFilterNodeOperators = "Selected" | "NotSelected" | "Inherited";
    
    • Selezionato: il valore è selezionato in modo esplicito.

    • NotSelected: il valore non è selezionato in modo esplicito.

    • Ereditato: la selezione del valore è in base al valore padre nella gerarchia o all'impostazione predefinita se è il valore radice.

Quando si definisce il filtro delle identità della gerarchia, tenere presenti le regole seguenti:

  • Acquisire le identità da DataView.
  • Ogni percorso di identità deve essere un percorso valido in DataView.
  • Ogni foglia deve avere un operatore Selected o NotSelected.
  • Per confrontare le identità, usare la funzione ICustomVisualsOpaqueUtils.compareCustomVisualOpaqueIdentities.
  • Le identità possono modificare le modifiche ai campi seguenti (ad esempio, l'aggiunta o la rimozione di campi). Power BI assegna le identità aggiornate al file filter.hierarchyData esistente.

Come usare l'API di filtro delle identità della gerarchia

Il codice seguente è un esempio di come usare l'API di filtro delle identità della gerarchia in un oggetto visivo personalizzato:

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

Per applicare il filtro, usare la chiamata API applyJsonFilter:

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

Per ripristinare il filtro JSON attivo, usare la proprietà jsonFilters disponibile in "VisualUpdateOptions":

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

Il filtro HierarchyIdnetity è supportato solo per i campi correlati gerarchicamente. Per impostazione predefinita, Power BI non convalida se i campi sono correlati gerarchicamente.

Per attivare la convalida gerarchicamente correlata, aggiungere la proprietà 'areHierarchicallyRelated' alla condizione del ruolo pertinente nel file capabilities.json:

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

I campi sono correlati gerarchicamente se vengono soddisfatte le condizioni seguenti:

  • Nessun limite di relazione incluso ha cardinalità molti a molti, né ConceptualNavigationBehavior.Weak.

  • Tutti i campi nel filtro sono presenti nel percorso.

  • Ogni relazione nel percorso ha la stessa direzione o è bidirezionale.

  • La direzione della relazione corrisponde alla cardinalità per uno a molti o bidirezionale.

Esempio di relazioni tra gerarchie

Ad esempio, data la relazione di entità seguente:

Diagramma che mostra la natura bidirezionale del filtro.

  • A, B sono correlati gerarchicamente: true
  • B, C sono correlati gerarchicamente: true
  • A, B, C sono correlati gerarchicamente: true
  • A, C, E sono correlati gerarchicamente: true (A --> E --> C)
  • A, B, E sono correlati gerarchicamente: true (B --> A --> E)
  • A, B, C, E sono correlati gerarchicamente: true (B --> A --> E --> C)
  • A, B, C, D sono correlati gerarchicamente: false (regola violata n. 3)
  • C, D sono correlati gerarchicamente: true
  • B, C, D sono correlati gerarchicamente: false (regola violata n. 3)
  • A, C, D, E sono correlati gerarchicamente: false (regola violata n. 3)

Nota

  • Quando queste convalide sono abilitate e i campi non sono correlati gerarchicamente, l'oggetto visivo non eseguirà il rendering e verrà visualizzato un messaggio di errore:

    Screenshot dell'oggetto visivo con le convalide abilitate per il caricamento perché i campi non sono correlati gerarchicamente. Il messaggio di errore indica che si usano campi che non dispongono di un set di relazioni supportato.

    Screenshot del messaggio di errore quando le convalide sono abilitate e i campi non sono correlati gerarchicamente. Il messaggio indica che non è possibile visualizzare questo oggetto visivo.

  • Quando queste convalide sono disabilitate e l'oggetto visivo filtro applica un filtro che contiene nodi correlati a campi non correlati in modo gerarchico, altri oggetti visivi potrebbero non essere visualizzati correttamente quando le misure sono in uso:

    Screenshot dell'oggetto visivo con le convalide disabilitate durante il caricamento perché i campi non sono correlati gerarchicamente. Il messaggio di errore indica che non è stato possibile caricare i dati per questo oggetto visivo.

    Screenshot del messaggio di errore quando le convalide sono disabilitate e i campi non sono correlati gerarchicamente. Il messaggio indica che non è stato possibile caricare i dati per questo oggetto visivo.

Esempio di codice per l'aggiornamento dell'albero dei dati della gerarchia dopo la nuova selezione

Il codice seguente illustra come aggiornare l'albero hierarchyData dopo una nuova selezione:

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

Considerazioni e limitazioni

  • Questo filtro è supportato solo per il mapping dataView matrice.

  • L'oggetto visivo deve contenere un solo ruolo dati di raggruppamento.

  • Un oggetto visivo che usa il tipo di filtro Identità gerarchia deve applicare solo un singolo filtro di questo tipo.