Udostępnij za pośrednictwem


Interfejs API filtrowania tożsamości hierarchicznej w wizualizacjach usługi Power BI

Interfejs API filtrowania tożsamości hierarchii umożliwia wizualizacjom, które używają mapowania widoku danych macierzy do filtrowania danych na wielu polach jednocześnie na podstawie punktów danych korzystających ze struktury hierarchii.

Ten interfejs API jest przydatny w następujących scenariuszach:

  • Filtrowanie hierarchii na podstawie punktów danych
  • Wizualizacje niestandardowe korzystające z semantycznych modeli z grupą na kluczach

Uwaga

Interfejs API filtrowania tożsamości hierarchii jest dostępny z interfejsu API w wersji 5.9.0

Interfejs filtru jest wyświetlany w następującym kodzie:

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

  • filterType: FilterType.HierarchyIdentity (dziedziczona z IFilter)

  • target: Tablica odpowiednich kolumn w zapytaniu. Obecnie obsługiwana jest tylko jedna rola; dlatego cel nie jest wymagany i powinien być pusty.

  • hierarchyData: wybrane i niezaznaczone elementy w drzewie hierarchii, w którym każdy IHierarchyIdentityFilterNode<IdentityType> reprezentuje wybór pojedynczej wartości.

type IHierarchyIdentityFilterTarget = IQueryNameTarget[]

interface IQueryNameTarget {
    queryName: string;
}
  • queryName: nazwa zapytania kolumny źródłowej w zapytaniu. Pochodzi z DataViewMetadataColumn
interface IHierarchyIdentityFilterNode<IdentityType> {
    identity: IdentityType;
    children?: IHierarchyIdentityFilterNode<IdentityType>[];
    operator: HierarchyFilterNodeOperators;
}
  • identity: tożsamość węzła w usłudze DataView. Wartość powinna być następująca:IdentityTypeCustomVisualOpaqueIdentity

  • podrzędne: Lista elementów podrzędnych węzłów istotnych dla bieżącego zaznaczenia

  • operator: operator dla pojedynczych obiektów w drzewie. Operator może być jedną z następujących trzech opcji:

    type HierarchyFilterNodeOperators = "Selected" | "NotSelected" | "Inherited";
    
    • Wybrane: wartość jest jawnie zaznaczona.

    • NotSelected: wartość nie jest jawnie zaznaczona.

    • Dziedziczone: wybór wartości jest zgodny z wartością nadrzędną w hierarchii lub wartością domyślną, jeśli jest to wartość główna.

Podczas definiowania filtru tożsamości hierarchii należy pamiętać o następujących regułach:

  • Pobierz tożsamości z widoku DataView.
  • Każda ścieżka tożsamości powinna być prawidłową ścieżką w widoku DataView.
  • Każdy liść powinien mieć operator Selected lub NotSelected.
  • Aby porównać tożsamości, użyj ICustomVisualsOpaqueUtils.compareCustomVisualOpaqueIdentities funkcji .
  • Tożsamości mogą ulec zmianie po zmianach w następujących polach (na przykład do dodawania lub usuwania pól). Usługa Power BI przypisuje zaktualizowane tożsamości do istniejącego pliku filter.hierarchyData.

Jak używać interfejsu API filtru tożsamości hierarchii

Poniższy kod to przykład użycia interfejsu API filtrowania tożsamości hierarchii w wizualizacji niestandardowej:

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

Aby zastosować filtr, użyj wywołania interfejsu applyJsonFilter API:

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

Aby przywrócić aktywny filtr JSON, użyj jsonFilters właściwości znalezionej w obszarze "VisualUpdateOptions":

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

Filtr HierarchyIdnetity jest obsługiwany tylko w przypadku pól powiązanych hierarchicznie. Domyślnie usługa Power BI nie sprawdza, czy pola są powiązane hierarchicznie.

Aby aktywować walidację powiązaną hierarchicznie, dodaj właściwość "areHierarchicallyRelated" do odpowiedniego warunku roli w pliku capabilities.json:

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

Pola są hierarchicznie powiązane, jeśli spełnione są następujące warunki:

  • Żadna uwzględniona krawędź relacji nie jest wiele do wielu kardynalności, ani ConceptualNavigationBehavior.Weak.

  • Wszystkie pola w filtrze istnieją w ścieżce.

  • Każda relacja w ścieżce ma ten sam kierunek lub dwukierunkowy.

  • Kierunek relacji odpowiada kardynalności jednej do wielu lub dwukierunkowych.

Przykład relacji hierarchii

Na przykład biorąc pod uwagę następującą relację jednostki:

Diagram przedstawiający dwukierunkowy charakter filtru.

  • A, B są powiązane hierarchicznie: prawda
  • B, C są powiązane hierarchicznie: prawda
  • A, B, C są powiązane hierarchicznie: prawda
  • A, C, E są powiązane hierarchicznie: true (A --> E --> C)
  • A, B, E są powiązane hierarchicznie: true (B --> A --> E)
  • A, B, C, E są powiązane hierarchicznie: true (B --> A --> E --> C)
  • A, B, C, D są powiązane hierarchicznie: false (naruszona reguła nr 3)
  • C, D są powiązane hierarchicznie: true
  • B, C, D są powiązane hierarchicznie: false (naruszona reguła nr 3)
  • A, C, D, E są powiązane hierarchicznie: false (naruszona reguła nr 3)

Uwaga

  • Po włączeniu tych walidacji pola nie są powiązane hierarchicznie, wizualizacja nie będzie renderowana, a zostanie wyświetlony komunikat o błędzie:

    Zrzut ekranu wizualizacji z włączonymi walidacjami nie można załadować, ponieważ pola nie są powiązane hierarchicznie. Komunikat o błędzie mówi:

    Zrzut ekranu przedstawiający komunikat o błędzie po włączeniu walidacji, a pola nie są powiązane hierarchicznie. Komunikat

  • Gdy te walidacje są wyłączone, a wizualizacja filtru stosuje filtr, który zawiera węzły związane z polami nie hierarchicznie powiązanymi, inne wizualizacje mogą nie być poprawnie renderowane, gdy miary są używane:

    Zrzut ekranu wizualizacji z wyłączonymi walidacjami nie można załadować, ponieważ pola nie są powiązane hierarchicznie. Komunikat o błędzie wskazuje, że

    Zrzut ekranu przedstawiający komunikat o błędzie, gdy walidacje są wyłączone, a pola nie są powiązane hierarchicznie. Komunikat zawiera komunikat

Przykład kodu służącego do aktualizowania drzewa danych hierarchii po nowym zaznaczeniu

Poniższy kod pokazuje, jak zaktualizować hierarchyData drzewo po nowym zaznaczeniu:

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

Rozważania i ograniczenia

  • Ten filtr jest obsługiwany tylko w przypadku mapowania widoku danych macierzy.

  • Wizualizacja powinna zawierać tylko jedną rolę danych grupowania.

  • Wizualizacja korzystająca z typu filtru tożsamości hierarchii powinna mieć zastosowanie tylko jednego filtru tego typu.