import React, { ReactNode, useMemo } from 'react';
import { INestNode } from './interface';
import { IField } from '../../interfaces';
import { MinusCircleIcon, PlusCircleIcon } from '@heroicons/react/24/outline';
import { formatDate, getMeaAggName } from '@/utils';
import { parsedOffsetDate } from '@/lib/op/offset';
function getChildCount(node: INestNode): number {
if (node.isCollapsed || node.children.length === 0) {
return 1;
}
return node.children.map(getChildCount).reduce((a, b) => a + b, 0);
}
/**
* render pivot table left tree table
* @param node
* @param dimsInCol
* @param depth
* @param cellRows
* @returns
*/
function renderTree(
node: INestNode,
dimsInCol: IField[],
depth: number,
cellRows: ReactNode[][],
meaNumber: number,
onHeaderCollapse: (node: INestNode) => void,
enableCollapse: boolean,
displayOffset?: number
) {
const childrenSize = getChildCount(node);
const { isCollapsed } = node;
if (depth > dimsInCol.length) {
return;
}
const field = depth > 0 ? dimsInCol[depth - 1] : undefined;
const formatter =
node.kind === 'value' && field?.semanticType === 'temporal'
? (x) => formatDate(parsedOffsetDate(displayOffset, field.offset)(x))
: (x) => `${x}`;
cellRows[depth].push(
{formatter(node.value)}
{node.kind === 'value' && node.height > 0 && enableCollapse && (
)}
|
);
if (isCollapsed) return;
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
renderTree(child, dimsInCol, depth + 1, cellRows, meaNumber, onHeaderCollapse, enableCollapse, displayOffset);
}
}
export interface TreeProps {
data: INestNode;
dimsInCol: IField[];
measInCol: IField[];
dimsInRow: IField[];
measInRow: IField[];
onHeaderCollapse: (node: INestNode) => void;
enableCollapse: boolean;
defaultAggregated: boolean;
displayOffset?: number;
}
const TopTree: React.FC = (props) => {
const { data, dimsInCol, measInCol, dimsInRow, measInRow, onHeaderCollapse } = props;
const nodeCells: ReactNode[][] = useMemo(() => {
const cellRows: ReactNode[][] = new Array(dimsInCol.length + 1).fill(0).map(() => []);
renderTree(data, dimsInCol, 0, cellRows, measInCol.length, onHeaderCollapse, props.enableCollapse, props.displayOffset);
const totalChildrenSize = getChildCount(data);
// if all children in one layer are collapsed, then we need to reset the rowSpan of all children to 1
cellRows.forEach((row: ReactNode[], rowIdx: number) => {
const rowSpanArr = row.map((child) => (React.isValidElement(child) ? (child.props as { rowSpan: number }).rowSpan : 0));
if (rowSpanArr.length > 0 && rowSpanArr[0] > 1 && rowSpanArr.every((v) => v === rowSpanArr[0])) {
row.forEach((childObj, childIdx) => {
if (React.isValidElement(childObj)) {
const newChild = React.cloneElement(childObj, { ...(childObj.props as any), rowSpan: 1 });
cellRows[rowIdx][childIdx] = newChild;
}
});
}
});
cellRows.push(
new Array(totalChildrenSize).fill(0).flatMap((ele, idx) =>
measInCol.map((m) => (
{props.defaultAggregated ? getMeaAggName(m.name, m.aggName) : m.name}
|
))
)
);
cellRows.shift();
const visibleRows = cellRows.filter((row) => row.length > 0);
if (visibleRows.length === 0) {
visibleRows.push([
Value
| ,
]);
}
const rowHeaderColumnCount = dimsInRow.length + (measInRow.length > 0 ? 1 : 0);
return visibleRows.map((row, rowIndex) => {
if (rowHeaderColumnCount === 0) {
return row;
}
if (rowIndex < visibleRows.length - 1) {
return [
| ,
...row,
];
}
return [
...dimsInRow.map((field) => (
{field.name}
|
)),
...(measInRow.length > 0
? [
Measure
| ,
]
: []),
...row,
];
});
}, [data, dimsInCol, measInCol, dimsInRow, measInRow, onHeaderCollapse, props.defaultAggregated, props.enableCollapse, props.displayOffset]);
return (
{nodeCells.map((row, rIndex) => (
{row}
))}
);
};
export default TopTree;