Hierarkkisten käyttäjätietojen suodattimien ohjelmointirajapinta Power BI:n visualisoinneissa
Hierarkian käyttäjätietojen suodattimen ohjelmointirajapinnan avulla visualisoinnit, jotka käyttävät Matriisitietonäkymän yhdistämismääritystä, voivat suodattaa tietoja useista kentistä kerralla hierarkiarakennetta käyttävien arvopisteiden perusteella.
Tästä ohjelmointirajapinnasta on hyötyä seuraavissa tilanteissa:
- Hierarkioiden suodattaminen arvopisteiden perusteella
- Mukautetut visualisoinnit, jotka käyttävät semanttisia malleja ja ryhmä avaimia
Muistiinpano
Hierarkian käyttäjätietojen suodattimen ohjelmointirajapinta on käytettävissä ohjelmointirajapinnan versiosta 5.9.0 alkaen
Suodatinliittymä näkyy seuraavassa koodissa:
interface IHierarchyIdentityFilter<IdentityType> extends IFilter {
target: IHierarchyIdentityFilterTarget;
hierarchyData: IHierarchyIdentityFilterNode<IdentityType>[];
}
$schema:
https://powerbi.com/product/schema#hierarchyIdentity
(peritty IFilter-suodattimesta)filterType: FilterType.HierarchyIdentity (peritty IFilter-kohteesta)
target: Kyselyn asiaankuuluvien sarakkeiden matriisi. Tällä hetkellä tuetaan vain yhtä roolia. siksi kohdetta ei tarvita, ja sen pitäisi olla tyhjä.
hierarchyData: valitut ja valitsemattomat kohteet hierarkiapuussa, jossa kukin
IHierarchyIdentityFilterNode<IdentityType>
edustaa yksittäistä arvon valintaa.
type IHierarchyIdentityFilterTarget = IQueryNameTarget[]
interface IQueryNameTarget {
queryName: string;
}
- queryName: kyselyn lähdesarakkeen kyselyn nimi. Se on peräisin
DataViewMetadataColumn
interface IHierarchyIdentityFilterNode<IdentityType> {
identity: IdentityType;
children?: IHierarchyIdentityFilterNode<IdentityType>[];
operator: HierarchyFilterNodeOperators;
}
identity: DataView-solmun käyttäjätiedot. Pitäisi
IdentityType
ollaCustomVisualOpaqueIdentity
aliluettelo nykyiseen valintaan liittyvistä solmun aliluetteloista
-operaattori: Puun yksittäisten objektien operaattori. -operaattori voi olla yksi seuraavista kolmesta vaihtoehdosta:
type HierarchyFilterNodeOperators = "Selected" | "NotSelected" | "Inherited";
Valittu: arvo on valittu eksplisiittisesti.
Ei valittu: arvoa ei ole valittu.
Peritty: arvon valinta perustuu hierarkian pääarvoon tai oletusarvoon, jos se on pääarvo.
Ota seuraavat säännöt huomioon hierarkian käyttäjätietojen suodatinta määritettäessä:
- Tutustu DataView'n käyttäjätietoihin.
- Kunkin käyttäjätietopolun on oltava kelvollinen polku DataView-kohteessa.
- Jokaisella lehdillä tulee olla operaattori valittuna tai Ei valittuna.
- Voit vertailla käyttäjätietoja käyttämällä -funktiota
ICustomVisualsOpaqueUtils.compareCustomVisualOpaqueIdentities
. - Käyttäjätiedot saattavat muuttua seuraavien kenttien muutosten mukaisesti (esimerkiksi lisäämällä tai poistamalla kenttiä). Power BI määrittää päivitetyt käyttäjätiedot olemassa olevalle filter.hierarchyData-tiedolle.
Hierarkian käyttäjätietojen suodattimen ohjelmointirajapinnan käyttäminen
Seuraava koodi on esimerkki siitä, miten voit käyttää hierarkian käyttäjätietojen suodattimen ohjelmointirajapintaa mukautetussa visualisoinnissa:
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();
Voit käyttää suodatinta ohjelmointirajapinnan kutsulla applyJsonFilter
:
this.host.applyJsonFilter(filter, "general", "filter", action);
Voit palauttaa aktiivisen JSON-suodattimen jsonFilters
käyttämällä "VisualUpdateOptions"-ominaisuuden avulla seuraavaa ominaisuutta:
export interface VisualUpdateOptions extends extensibility.VisualUpdateOptions {
//...
jsonFilters?: IFilter[];
}
Hierarkiaan liittyvien kenttien vahvistus (valinnainen)
HierarchyIdnetity
Suodatinta tuetaan vain hierarkkisissa kentissä. Oletusarvoisesti Power BI ei vahvista, liittyvätkö kentät hierarkkisesti toisiinsa.
Aktivoi hierarkkisesti liittyvä vahvistus lisäämällä areHierarchicallyRelated-ominaisuus capabilities.json tiedoston asianmukaiseen rooliehtoon:
"dataViewMappings": [
{
"conditions": [
{
"Rows": {
"min": 1,
"areHierarchicallyRelated": true <------ NEW ------>
},
"Value": {
"min": 0
}
}
],
...
}
]
Kentät liittyvät hierarkkisesti toisiinsa, jos seuraavat ehdot täyttyvät:
Sisältyneen suhteen reuna on monta moneen -kardinaliteetti, tai
ConceptualNavigationBehavior.Weak
.Kaikki suodattimen kentät ovat olemassa polussa.
Jokainen polun suhde on samansuuntainen tai kaksisuuntainen.
Suhteen suunta vastaa kardinaliteettia, joka on yksi moneen tai kaksisuuntainen.
Esimerkki hierarkiasuhteista
Otetaan esimerkiksi seuraava entiteettisuhde:
- A, B liittyvät hierarkkisesti toisiinsa: true
- B, C liittyvät hierarkkisesti toisiinsa: true
- A, B, C liittyvät hierarkkisesti: true
- A, C, E liittyvät hierarkkisesti: tosi (A --> E --> C)
- A, B, E liittyvät hierarkkisesti: true (B --> A --> E)
- A, B, C, E liittyvät hierarkkisesti: true (B --> A --> E --> C)
- A, B, C, D liittyvät hierarkkisesti: false (rikkoi sääntöä 3)
- C, D liittyvät hierarkkisesti toisiinsa: true
- B, C, D liittyvät hierarkkisesti: false (rikkoi sääntöä 3)
- A, C, D, E liittyvät hierarkkisesti toisiinsa: false (rikkoi sääntöä 3)
Muistiinpano
Kun nämä vahvistukset ovat käytössä eivätkä kentät ole hierarkkisesti yhteydessä toisiinsa, visualisointia ei hahmonneta ja näyttöön tulee virhesanoma:
Kun nämä vahvistukset on poistettu käytöstä ja suodatinvisualisointi käyttää suodatinta, joka sisältää ei-hierarkkisesti toisiinsa liittyviin kenttiin liittyviä solmuja, muita visualisointeja ei ehkä hahmonneta oikein, kun mittarit ovat käytössä:
Koodiesimerkki hierarkiatietopuun päivittämiseen uuden valinnan jälkeen
Seuraava koodi näyttää, miten puu päivitetään hierarchyData
uuden valinnan jälkeen:
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();
}