Поделиться через


API иерархических удостоверений фильтрует в визуальных элементах Power BI

API фильтра удостоверений иерархии позволяет визуальным элементам, используюющим сопоставление Matrix DataView, фильтровать данные по нескольким полям за раз на основе точек данных, использующих структуру иерархии.

Этот API полезен в следующих сценариях:

  • Фильтрация иерархий на основе точек данных
  • Пользовательские визуальные элементы, использующие семантические модели с группой по ключам

Примечание.

API фильтра удостоверений иерархии доступен из API версии 5.9.0

Интерфейс фильтра показан в следующем коде:

interface IHierarchyIdentityFilter<IdentityType> extends IFilter {
    target: IHierarchyIdentityFilterTarget;
    hierarchyData: IHierarchyIdentityFilterNode<IdentityType>[];
}
  • $schema: https://powerbi.com/product/schema#hierarchyIdentity (наследуется от IFilter)

  • filterType: FilterType.HierarchyIdentity (наследуется от IFilter)

  • целевой объект: массив соответствующих столбцов в запросе. В настоящее время поддерживается только одна роль; поэтому целевой объект не является обязательным и должен быть пустым.

  • hierarchyData: выбранные и неизбираемые элементы в дереве иерархии, где каждый IHierarchyIdentityFilterNode<IdentityType> представляет один выбор значения.

type IHierarchyIdentityFilterTarget = IQueryNameTarget[]

interface IQueryNameTarget {
    queryName: string;
}
  • queryName: имя запроса исходного столбца в запросе. Это происходит от DataViewMetadataColumn
interface IHierarchyIdentityFilterNode<IdentityType> {
    identity: IdentityType;
    children?: IHierarchyIdentityFilterNode<IdentityType>[];
    operator: HierarchyFilterNodeOperators;
}
  • удостоверение: удостоверение узла в DataView. Должно IdentityType быть CustomVisualOpaqueIdentity

  • дочерние элементы: список дочерних элементов узла, относящихся к текущему выбору

  • оператор: оператор для отдельных объектов в дереве. Оператор может быть одним из следующих трех вариантов:

    type HierarchyFilterNodeOperators = "Selected" | "NotSelected" | "Inherited";
    
    • Выбрано: значение явно выбрано.

    • NotSelected: значение явно не выбрано.

    • Наследуется: выбор значения соответствует родительскому значению в иерархии или по умолчанию, если это корневое значение.

При определении фильтра удостоверений иерархии следует учитывать следующие правила:

  • Возьмите удостоверения из DataView.
  • Каждый путь удостоверения должен быть допустимым путем в DataView.
  • Каждый лист должен иметь оператор Selected или NotSelected.
  • Чтобы сравнить удостоверения, используйте функцию ICustomVisualsOpaqueUtils.compareCustomVisualOpaqueIdentities .
  • Удостоверения могут изменяться после изменений полей (например, добавление или удаление полей). Power BI назначает обновленные удостоверения существующим filter.hierarchyData.

Использование API фильтра удостоверений иерархии

Следующий код является примером использования API фильтрации удостоверений иерархии в пользовательском визуальном элементе:

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

Чтобы применить фильтр, используйте applyJsonFilter вызов API:

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

Чтобы восстановить активный фильтр JSON, используйте свойство, найденное jsonFilters в visualUpdateOptions:

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

Фильтр HierarchyIdnetity поддерживается только для иерархически связанных полей. По умолчанию Power BI не проверяет, связаны ли поля иерархически.

Чтобы активировать иерархическую проверку, добавьте свойство areHierarchicallyRelated в соответствующее условие роли в файле capabilities.json:

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

Поля иерархически связаны, если выполняются следующие условия:

  • Нет границы связи, которые не включаются во многих карта inality, или ConceptualNavigationBehavior.Weak.

  • Все поля в фильтре существуют в пути.

  • Каждая связь в пути имеет одинаковое направление или двунаправленное.

  • Направление связи соответствует карта inality для одного или нескольких или двунаправленных.

Пример связей иерархии

Например, учитывая следующую связь сущности:

Схема, показывающая двунаправленный характер фильтра.

  • A, B иерархически связаны: true
  • B, C иерархически связаны: true
  • A, B, C иерархически связаны: true
  • A, C, E иерархически связаны: true (A --> E --> C)
  • A, B, E иерархически связаны: true (B --> A --> E)
  • A, B, C, E иерархически связаны: true (B --> A --> E --> C)
  • A, B, C, D иерархически связаны: false (нарушенное правило #3)
  • C, D иерархически связаны: true
  • B, C, D иерархически связаны: false (нарушенное правило #3)
  • A, C, D, E иерархически связаны: false (нарушенное правило #3)

Примечание.

  • Если эти проверки включены, и поля не связаны иерархически, визуальный элемент не будет отображаться, и появится сообщение об ошибке:

    Снимок экрана: визуальный элемент с включенными проверками не удалось загрузить, так как поля не связаны иерархически. В сообщении об ошибке говорится, что вы используете поля, которые не имеют поддерживаемого набора связей.

    Снимок экрана: сообщение об ошибке при включении проверок, а поля не связаны иерархически. В сообщении говорится, что не удается отобразить этот визуальный элемент.

  • Если эти проверки отключены, и визуальный элемент фильтра применяет фильтр, содержащий узлы, связанные с не иерархически связанными полями, другие визуальные элементы могут неправильно отображаться при использовании мер:

    Снимок экрана: визуальный элемент с отключенными проверками, не удалось загрузить, так как поля не связаны иерархически. Сообщение об ошибке говорит, что не удалось загрузить данные для этого визуального элемента.

    Снимок экрана: сообщение об ошибке при отключении проверок и поля не связаны иерархически. В сообщении говорится, что не удалось загрузить данные для этого визуального элемента.

Пример кода для обновления дерева данных иерархии после нового выбора

В следующем коде показано, как обновить hierarchyData дерево после создания выбора:

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

Рекомендации и ограничения

  • Этот фильтр поддерживается только для сопоставления матрицы dataView.

  • Визуальный элемент должен содержать только одну роль группированияданных.

  • Визуальный элемент, использующий тип фильтра удостоверений иерархии, должен применять только один фильтр этого типа.