API ตัวกรองข้อมูลประจําตัวแบบลําดับชั้นในวิชวล Power BI
API ตัวกรองข้อมูลประจําตัวของลําดับชั้นช่วยให้วิชวลที่ใช้การแมปข้อมูลเมทริกซ์มุมมองข้อมูลเพื่อกรองข้อมูลบนหลายเขตข้อมูลพร้อมกันตามจุดข้อมูลที่ใช้โครงสร้างลําดับชั้น
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 จะไม่ตรวจสอบว่าเขตข้อมูลมีความเกี่ยวข้องตามลําดับชั้นหรือไม่
เมื่อต้องการเปิดใช้งานการตรวจสอบความถูกต้องที่เกี่ยวข้องตามลําดับชั้น ให้เพิ่มคุณสมบัติ 'isHierarchlyRelated' ไปยังเงื่อนไขบทบาทที่เกี่ยวข้องในไฟล์ capabilities.json:
"dataViewMappings": [
{
"conditions": [
{
"Rows": {
"min": 1,
"areHierarchicallyRelated": true <------ NEW ------>
},
"Value": {
"min": 0
}
}
],
...
}
]
เขตข้อมูลจะเกี่ยวข้องกันตามลําดับชั้นถ้าเป็นไปตามเงื่อนไขต่อไปนี้:
ไม่มีขอบความสัมพันธ์ที่รวมอยู่กลุ่มต่อกลุ่มคาร์ดินาลลิตี้ หรือ
ConceptualNavigationBehavior.Weak
เขตข้อมูลทั้งหมดในตัวกรองมีอยู่ในเส้นทาง
ทุกความสัมพันธ์ในเส้นทางจะมีทิศทางเดียวกันหรือสองทิศทาง
ทิศทางของความสัมพันธ์ตรงกับคาร์ดินาลลิตี้สําหรับหนึ่งต่อกลุ่มหรือสองทิศทาง
ตัวอย่างของความสัมพันธ์ของลําดับชั้น
ตัวอย่างเช่น ได้รับความสัมพันธ์ของเอนทิตีต่อไปนี้:
- A, B มีความเกี่ยวข้องตามลําดับชั้น: จริง
- B, C มีความเกี่ยวข้องตามลําดับชั้น: true
- A, B, C มีความเกี่ยวข้องตามลําดับชั้น: true
- A, C, มีความเกี่ยวข้องตามลําดับชั้น: true (A --> --> C)
- A, B, มีความเกี่ยวข้องตามลําดับชั้น: true (B --> A -->
- A, B, C, มีความเกี่ยวข้องตามลําดับชั้น: true (B --> A --> --> C)
- A, B, C, D มีความเกี่ยวข้องตามลําดับชั้น: เท็จ (กฎที่ถูกฝ่าฝืน #3)
- C, D มีความเกี่ยวข้องตามลําดับชั้น: true
- B, C, D มีความเกี่ยวข้องตามลําดับชั้น: เท็จ (กฎที่ถูกฝ่าฝืน #3)
- A, C, D, มีความเกี่ยวข้องตามลําดับชั้น: เท็จ (กฎที่ถูกฝ่าฝืน #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 เมทริกซ์เท่านั้น
วิชวลควรประกอบด้วยบทบาทการจัดกลุ่มข้อมูลเพียงบทบาทเดียวเท่านั้น
วิชวลที่ใช้ชนิดตัวกรองข้อมูลเฉพาะตัวของลําดับชั้นควรใช้ตัวกรองชนิดนี้เพียงตัวกรองเดียวเท่านั้น