feat: support multiple column dimensions in dynamic reports
Combine column dimensions into composite keys instead of using only the first column dimension, enabling richer pivot tables and charts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
945985c969
commit
04ec221808
3 changed files with 32 additions and 19 deletions
|
|
@ -36,19 +36,25 @@ export default function DynamicReportChart({ config, result }: DynamicReportChar
|
|||
return { chartData: [], seriesKeys: [], seriesColors: {} };
|
||||
}
|
||||
|
||||
const colDim = config.columns[0];
|
||||
const colDims = config.columns;
|
||||
const rowDim = config.rows[0];
|
||||
const measure = config.values[0] || "periodic";
|
||||
|
||||
// X-axis = first column dimension (or first row dimension if no columns)
|
||||
const xDim = colDim || rowDim;
|
||||
if (!xDim) return { chartData: [], seriesKeys: [], seriesColors: {} };
|
||||
// X-axis = composite column key (or first row dimension if no columns)
|
||||
const hasColDims = colDims.length > 0;
|
||||
if (!hasColDims && !rowDim) return { chartData: [], seriesKeys: [], seriesColors: {} };
|
||||
|
||||
// Build composite column key per row
|
||||
const getColKey = (r: typeof result.rows[0]) =>
|
||||
colDims.map((d) => r.keys[d] || "").join(" — ");
|
||||
|
||||
// Series = first row dimension (or no stacking if no rows, or first row if columns exist)
|
||||
const seriesDim = colDim ? rowDim : undefined;
|
||||
const seriesDim = hasColDims ? rowDim : undefined;
|
||||
|
||||
// Collect unique x and series values
|
||||
const xValues = [...new Set(result.rows.map((r) => r.keys[xDim]))].sort();
|
||||
const xValues = hasColDims
|
||||
? [...new Set(result.rows.map(getColKey))].sort()
|
||||
: [...new Set(result.rows.map((r) => r.keys[rowDim]))].sort();
|
||||
const seriesVals = seriesDim
|
||||
? [...new Set(result.rows.map((r) => r.keys[seriesDim]))].sort()
|
||||
: [measure];
|
||||
|
|
@ -59,12 +65,14 @@ export default function DynamicReportChart({ config, result }: DynamicReportChar
|
|||
if (seriesDim) {
|
||||
for (const sv of seriesVals) {
|
||||
const matchingRows = result.rows.filter(
|
||||
(r) => r.keys[xDim] === xVal && r.keys[seriesDim] === sv
|
||||
(r) => (hasColDims ? getColKey(r) : r.keys[rowDim]) === xVal && r.keys[seriesDim] === sv
|
||||
);
|
||||
entry[sv] = matchingRows.reduce((sum, r) => sum + (r.measures[measure] || 0), 0);
|
||||
}
|
||||
} else {
|
||||
const matchingRows = result.rows.filter((r) => r.keys[xDim] === xVal);
|
||||
const matchingRows = result.rows.filter((r) =>
|
||||
hasColDims ? getColKey(r) === xVal : r.keys[rowDim] === xVal
|
||||
);
|
||||
entry[measure] = matchingRows.reduce((sum, r) => sum + (r.measures[measure] || 0), 0);
|
||||
}
|
||||
return entry;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ interface PivotedRow {
|
|||
function pivotRows(
|
||||
rows: PivotResultRow[],
|
||||
rowDims: string[],
|
||||
colDim: string | undefined,
|
||||
colDims: string[],
|
||||
measures: string[],
|
||||
): PivotedRow[] {
|
||||
const map = new Map<string, PivotedRow>();
|
||||
|
|
@ -38,7 +38,9 @@ function pivotRows(
|
|||
map.set(rowKey, pivoted);
|
||||
}
|
||||
|
||||
const colKey = colDim ? (row.keys[colDim] || "") : "__all__";
|
||||
const colKey = colDims.length > 0
|
||||
? colDims.map((d) => row.keys[d] || "").join("\0")
|
||||
: "__all__";
|
||||
if (!pivoted.cells[colKey]) pivoted.cells[colKey] = {};
|
||||
for (const m of measures) {
|
||||
pivoted.cells[colKey][m] = (pivoted.cells[colKey][m] || 0) + (row.measures[m] || 0);
|
||||
|
|
@ -107,14 +109,17 @@ export default function DynamicReportTable({ config, result }: DynamicReportTabl
|
|||
};
|
||||
|
||||
const rowDims = config.rows;
|
||||
const colDim = config.columns[0] || undefined;
|
||||
const colValues = colDim ? result.columnValues : ["__all__"];
|
||||
const colDims = config.columns;
|
||||
const colValues = colDims.length > 0 ? result.columnValues : ["__all__"];
|
||||
const measures = config.values;
|
||||
|
||||
// Display label for a composite column key (joined with \0)
|
||||
const colLabel = (compositeKey: string) => compositeKey.split("\0").join(" — ");
|
||||
|
||||
// Pivot the flat SQL rows into one PivotedRow per unique row-key combo
|
||||
const pivotedRows = useMemo(
|
||||
() => pivotRows(result.rows, rowDims, colDim, measures),
|
||||
[result.rows, rowDims, colDim, measures],
|
||||
() => pivotRows(result.rows, rowDims, colDims, measures),
|
||||
[result.rows, rowDims, colDims, measures],
|
||||
);
|
||||
|
||||
if (pivotedRows.length === 0) {
|
||||
|
|
@ -156,7 +161,7 @@ export default function DynamicReportTable({ config, result }: DynamicReportTabl
|
|||
{colValues.map((colVal) =>
|
||||
measures.map((m) => (
|
||||
<th key={`${colVal}-${m}`} className="text-right px-3 py-2 font-medium text-[var(--muted-foreground)] border-l border-[var(--border)]">
|
||||
{colDim ? (measures.length > 1 ? `${colVal} — ${measureLabel(m)}` : colVal) : measureLabel(m)}
|
||||
{colDims.length > 0 ? (measures.length > 1 ? `${colLabel(colVal)} — ${measureLabel(m)}` : colLabel(colVal)) : measureLabel(m)}
|
||||
</th>
|
||||
))
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -308,10 +308,10 @@ export async function getDynamicReportData(
|
|||
}
|
||||
}
|
||||
|
||||
// Extract distinct column values
|
||||
const columnDim = config.columns[0];
|
||||
const columnValues = columnDim
|
||||
? [...new Set(rows.map((r) => r.keys[columnDim]))].sort()
|
||||
// Extract distinct column values (composite key when multiple column dimensions)
|
||||
const colDims = config.columns;
|
||||
const columnValues = colDims.length > 0
|
||||
? [...new Set(rows.map((r) => colDims.map((d) => r.keys[d] || "").join("\0")))].sort()
|
||||
: [];
|
||||
|
||||
// Dimension labels
|
||||
|
|
|
|||
Loading…
Reference in a new issue