Sdílet prostřednictvím


Rozhraní API filtrů hierarchických identit ve vizuálech Power BI

Rozhraní API filtru identit hierarchie umožňuje vizuálům, které používají mapování Matice DataView k filtrování dat na více polích najednou na základě datových bodů, které používají strukturu hierarchie.

Toto rozhraní API je užitečné v následujících scénářích:

  • Filtrování hierarchií na základě datových bodů
  • Vlastní vizuály, které používají sémantické modely se skupinou na klíčích

Poznámka:

Rozhraní API filtru identit hierarchie je dostupné z rozhraní API verze 5.9.0.

Rozhraní filtru je zobrazeno v následujícím kódu:

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

  • filterType: FilterType.HierarchyIdentity (zděděno z IFilter)

  • target: Pole relevantních sloupců v dotazu. V současné době je podporována pouze jedna role; proto není cíl povinný a měl by být prázdný.

  • hierarchyData: vybrané a nevybrané položky ve stromu hierarchie, kde každý IHierarchyIdentityFilterNode<IdentityType> představuje jeden výběr hodnoty.

type IHierarchyIdentityFilterTarget = IQueryNameTarget[]

interface IQueryNameTarget {
    queryName: string;
}
  • queryName: název dotazu zdrojového sloupce v dotazu. Pochází z DataViewMetadataColumn
interface IHierarchyIdentityFilterNode<IdentityType> {
    identity: IdentityType;
    children?: IHierarchyIdentityFilterNode<IdentityType>[];
    operator: HierarchyFilterNodeOperators;
}
  • identity: Identita uzlu v DataView. Měla IdentityType by být CustomVisualOpaqueIdentity

  • podřízené položky: Seznam podřízených uzlů relevantních pro aktuální výběr

  • operator: Operátor pro jednotlivé objekty ve stromu. Operátor může být jednou z následujících tří možností:

    type HierarchyFilterNodeOperators = "Selected" | "NotSelected" | "Inherited";
    
    • Vybráno: hodnota je explicitně vybrána.

    • NotSelected: hodnota není explicitně vybrána.

    • Zděděno: Výběr hodnoty je v závislosti na nadřazené hodnotě v hierarchii nebo výchozí, pokud se jedná o kořenovou hodnotu.

Při definování filtru identit hierarchie mějte na paměti následující pravidla:

  • Převezměte identity z dataView.
  • Každá cesta identity by měla být platná cesta v objektu DataView.
  • Každý list by měl mít operátor Selected nebo NotSelected.
  • K porovnání identit použijte ICustomVisualsOpaqueUtils.compareCustomVisualOpaqueIdentities funkci.
  • Identity můžou změnit následující změny polí (například přidání nebo odebrání polí). Power BI přiřadí aktualizované identity existujícímu filter.hierarchyData.

Jak používat rozhraní API filtru identit hierarchie

Následující kód je příkladem použití rozhraní API filtru identit hierarchie ve vlastním vizuálu:

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

Pokud chcete použít filtr, použijte applyJsonFilter volání rozhraní API:

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

Chcete-li obnovit aktivní filtr JSON, použijte jsonFilters vlastnost nalezenou v části VisualUpdateOptions:

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

Filtr HierarchyIdnetity je podporován pouze pro hierarchicky související pole. Power BI ve výchozím nastavení neověří, jestli jsou pole hierarchicky propojená.

Pokud chcete aktivovat hierarchicky související ověřování, přidejte vlastnost areHierarchly Related do příslušné podmínky role v souboru capabilities.json:

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

Pole jsou hierarchicky související, pokud jsou splněny následující podmínky:

  • Žádná zahrnutá hrana relace není mnoho k mnoha kardinalitě, ani ConceptualNavigationBehavior.Weak.

  • Všechna pole ve filtru existují v cestě.

  • Každá relace v cestě má stejný směr nebo obousměrný směr.

  • Směr relace odpovídá kardinalitě pro jednu z mnoha nebo obousměrných hodnot.

Příklad vztahů hierarchie

Například s ohledem na následující vztah entity:

Diagram znázorňující obousměrnou povahu filtru

  • A, B jsou hierarchicky související: true
  • B, C jsou hierarchicky související: true
  • A, B, C jsou hierarchicky související: true
  • A, C, E jsou hierarchicky související: true (A --> E --> C)
  • A, B, E jsou hierarchicky související: true (B --> A --> E)
  • A, B, C, E jsou hierarchicky související: true (B --> A --> E --> C)
  • A, B, C, D jsou hierarchicky související: false (porušení pravidla č. 3)
  • C, D jsou hierarchicky související: true
  • B, C, D jsou hierarchicky související: false (porušení pravidla č. 3)
  • A, C, D, E jsou hierarchicky související: false (porušení pravidla č. 3)

Poznámka:

  • Pokud jsou tato ověření povolená a pole nejsou hierarchicky související, vizuál se nevykreslí a zobrazí se chybová zpráva:

    Snímek obrazovky vizuálu s povolenými ověřeními se nedaří načíst, protože pole nejsou hierarchicky související Chybová zpráva říká, že používáte pole, která nemají podporovanou sadu relací.

    Snímek obrazovky s chybovou zprávou, když jsou povolená ověření a pole nejsou hierarchicky související Zpráva říká, že tento vizuál nejde zobrazit.

  • Pokud jsou tato ověření zakázaná a vizuál filtru použije filtr, který obsahuje uzly související s ne hierarchicky souvisejícími poli, nemusí se ostatní vizuály při použití měr vykreslit správně:

    Snímek obrazovky vizuálu se zakázanými ověřeními se nedaří načíst, protože pole nejsou hierarchicky související Chybová zpráva říká, že se nepodařilo načíst data pro tento vizuál.

    Snímek obrazovky s chybovou zprávou, když jsou ověření zakázaná a pole nejsou hierarchicky propojená Zpráva říká, že se nepodařilo načíst data pro tento vizuál.

Příklad kódu pro aktualizaci datového stromu hierarchie po novém výběru

Následující kód ukazuje, jak aktualizovat hierarchyData strom po novém výběru:

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

Úvahy a omezení

  • Tento filtr je podporován pouze pro mapování maticového zobrazení dat.

  • Vizuál by měl obsahovat pouze jednu roli seskupování dat.

  • Vizuál, který používá typ filtru identity hierarchie, by měl použít pouze jeden filtr tohoto typu.