Compartir a través de


API de filtros de identidad jerárquica en objetos visuales de Power BI

La API de filtro de identidad de jerarquía permite a los objetos visuales que usan la asignación de DataView de matriz para filtrar datos en varios campos a la vez en función de los puntos de datos que usan una estructura de jerarquía.

Esta API es útil en los escenarios siguientes:

  • Filtrado de jerarquías basadas en puntos de datos
  • Objetos visuales personalizados que usen modelos semánticos con grupo en claves

Nota:

La API de filtro de identidad de jerarquía está disponible en la versión 5.9.0 de la API

En el código siguiente se muestra la interfaz de filtro:

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

  • filterType: FilterType.HierarchyIdentity (heredado de IFilter)

  • target: matriz de columnas pertinentes en la consulta. Actualmente solo se admite un solo rol; por lo tanto, el destino no es necesario y debe estar vacío.

  • hierarchyData: los elementos seleccionados y no seleccionados en un árbol de jerarquía donde cada IHierarchyIdentityFilterNode<IdentityType> representa una selección de valor único.

type IHierarchyIdentityFilterTarget = IQueryNameTarget[]

interface IQueryNameTarget {
    queryName: string;
}
  • queryName: nombre de consulta de la columna de origen de la consulta. Viene de DataViewMetadataColumn
interface IHierarchyIdentityFilterNode<IdentityType> {
    identity: IdentityType;
    children?: IHierarchyIdentityFilterNode<IdentityType>[];
    operator: HierarchyFilterNodeOperators;
}
  • identity: la identidad del nodo en DataView. IdentityType debe ser CustomVisualOpaqueIdentity

  • children: lista de elementos secundarios de nodo relevantes para la selección actual

  • operator: operador para objetos únicos en el árbol. El operador puede ser una de las tres opciones siguientes:

    type HierarchyFilterNodeOperators = "Selected" | "NotSelected" | "Inherited";
    
    • Selected: el valor se selecciona explícitamente.

    • NotSelected: el valor no está seleccionado explícitamente.

    • Inherited: la selección de valores se basa en el valor primario de la jerarquía o el valor predeterminado si es el valor raíz.

Tenga en cuenta las siguientes reglas al definir el filtro de identidad de la jerarquía:

  • Tome las identidades de DataView.
  • Cada ruta de identidad debe ser una ruta válida en DataView.
  • Cada hoja debe tener un operador de Selected o NotSelected.
  • Para comparar identidades, use la función ICustomVisualsOpaqueUtils.compareCustomVisualOpaqueIdentities.
  • Las identidades pueden cambiar los siguientes cambios en los campos (por ejemplo, agregar o quitar campos). Power BI asigna las identidades actualizadas a filter.hierarchyData existente.

Uso de la API de filtro de identidad de jerarquía

El código siguiente es un ejemplo de cómo usar la API de filtro de identidad de jerarquía en un objeto visual personalizado:

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

Para aplicar el filtro, use la llamada API applyJsonFilter:

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

Para restaurar el filtro JSON activo, use la propiedad jsonFilters que se encuentra en "VisualUpdateOptions":

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

El filtro HierarchyIdnetity solo se admite para campos relacionados jerárquicamente. De forma predeterminada, Power BI no valida si los campos están relacionados jerárquicamente.

Para activar la validación relacionada jerárquicamente, agregue la propiedad "areHierarchicallyRelated" a la condición de rol pertinente en el archivo de capabilities.json:

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

Los campos están relacionados jerárquicamente si se cumplen las condiciones siguientes:

  • Ningún borde de relación incluido es cardinalidad de muchos a muchos, ni ConceptualNavigationBehavior.Weak.

  • Todos los campos del filtro existen en la ruta de acceso.

  • Cada relación de la ruta de acceso tiene la misma dirección o bidireccional.

  • La dirección de la relación coincide con la cardinalidad de uno a varios o bidireccional.

Ejemplo de relaciones de jerarquía

Por ejemplo, dada la siguiente relación de entidad:

Diagrama que muestra la naturaleza bidireccional del filtro.

  • A, B están jerárquicamente relacionados: true
  • B, C están jerárquicamente relacionados: true
  • A, B, C están jerárquicamente relacionados: true
  • A, C, E están jerárquicamente relacionados: true (A --> E --> C)
  • A, B, E están jerárquicamente relacionados: true (B --> A --> E)
  • A, B, C, E están relacionados jerárquicamente: true (B --> A --> E --> C)
  • A, B, C, D están relacionados jerárquicamente: false (regla infringida n.º 3)
  • C, D están jerárquicamente relacionados: true
  • B, C, D están relacionados jerárquicamente: false (regla infringida n.º 3)
  • A, C, D, E están relacionados jerárquicamente: false (regla infringida n.º 3)

Nota:

  • Cuando estas validaciones están habilitadas y los campos no están relacionados jerárquicamente, el objeto visual no se representará y se mostrará un mensaje de error:

    Captura de pantalla del objeto visual con validaciones habilitadas que no se cargan porque los campos no están relacionados jerárquicamente. El mensaje de error indica

    Captura de pantalla del mensaje de error cuando las validaciones están habilitadas y los campos no están relacionados jerárquicamente. El mensaje indica

  • Cuando estas validaciones están deshabilitadas y el objeto visual de filtro aplica un filtro que contiene nodos relacionados con campos no jerárquicos, es posible que otros objetos visuales no se representen correctamente cuando se usen medidas:

    Captura de pantalla del objeto visual con validaciones deshabilitadas que no se cargan porque los campos no están relacionados jerárquicamente. El mensaje de error indica

    Captura de pantalla del mensaje de error cuando las validaciones están deshabilitadas y los campos no están relacionados jerárquicamente. El mensaje indica

Ejemplo de código para actualizar el árbol de datos de jerarquía después de una nueva selección

El código siguiente muestra cómo actualizar el árbol hierarchyData después de una nueva selección:

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

Consideraciones y limitaciones

  • Este filtro solo se admite para la asignación de dataView de matriz.

  • El objeto visual debe contener solo un rol de datos de agrupación.

  • Un objeto visual que use el tipo de filtro de identidad de jerarquía solo debe aplicar un solo filtro de este tipo.