Skip to content

Commit bc4308a

Browse files
committed
[IMP] chart: detect hierarchical chart at chart creation
With this commit, at the chart creation we will check if the data in the selected zone looks like a hierarchical dataset (multiple columns with string values, and one column with numeric values). If so, we will create a sunburst chart instead of a car chart. closes #6092 Task: 4724420 Signed-off-by: Pierre Rousseau (pro) <[email protected]>
1 parent 487af76 commit bc4308a

File tree

2 files changed

+98
-1
lines changed

2 files changed

+98
-1
lines changed

src/helpers/figures/charts/chart_factory.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
UID,
1414
Zone,
1515
} from "../../../types";
16-
import { LineChartDefinition } from "../../../types/chart";
16+
import { LineChartDefinition, SunburstChartDefinition } from "../../../types/chart";
1717
import { ChartDefinition, ChartRuntime } from "../../../types/chart/chart";
1818
import { CoreGetters, Getters } from "../../../types/getters";
1919
import { Validator } from "../../../types/validator";
@@ -92,10 +92,15 @@ export function transformDefinition(
9292
*
9393
* The type of chart will be :
9494
* - If the zone is a single non-empty cell, returns a scorecard
95+
* - If the dataset starts with multiple string columns, returns a sunburst chart
9596
* - If the all the labels are numbers/date, returns a line chart
9697
* - Else returns a bar chart
9798
*/
9899
export function getSmartChartDefinition(zone: Zone, getters: Getters): ChartDefinition {
100+
const hierarchicalDefinition = tryToMakeHierarchicalChart(getters, zone);
101+
if (hierarchicalDefinition) {
102+
return hierarchicalDefinition;
103+
}
99104
const sheetId = getters.getActiveSheetId();
100105
let dataSetZone = zone;
101106
const singleColumn = zoneToDimension(zone).numberOfCols === 1;
@@ -175,3 +180,56 @@ export function getSmartChartDefinition(zone: Zone, getters: Getters): ChartDefi
175180
legendPosition: newLegendPos,
176181
};
177182
}
183+
184+
/**
185+
* Return a sunburst chart definition if the data in the zone looks like hierarchical data.
186+
*/
187+
function tryToMakeHierarchicalChart(
188+
getters: Getters,
189+
zone: Zone
190+
): SunburstChartDefinition | undefined {
191+
const sheetId = getters.getActiveSheetId();
192+
const numberOfCols = zoneToDimension(zone).numberOfCols;
193+
if (zone.top === zone.bottom || numberOfCols <= 2) {
194+
return undefined;
195+
}
196+
197+
const firstCellOfValues = getters.getEvaluatedCell({ sheetId, col: zone.right, row: zone.top });
198+
const dataSetsHaveTitle = firstCellOfValues.type !== CellValueType.number;
199+
200+
const getColumnType = (col: number) => {
201+
const cells = getters.getEvaluatedCellsInZone(sheetId, {
202+
...zone,
203+
left: col,
204+
right: col,
205+
top: zone.top + (dataSetsHaveTitle ? 1 : 0),
206+
});
207+
if (cells.every((cell) => cell.type !== CellValueType.number)) {
208+
return "string";
209+
} else if (cells.every((cell) => cell.type !== CellValueType.text)) {
210+
return "number";
211+
}
212+
return undefined;
213+
};
214+
215+
for (let col = zone.left; col < zone.right; col++) {
216+
const columnType = getColumnType(col);
217+
if (col !== zone.right && columnType !== "string") {
218+
return undefined;
219+
} else if (col === zone.right && columnType !== "number") {
220+
return undefined;
221+
}
222+
}
223+
224+
const dataSetZone = { ...zone, right: zone.right - 1 };
225+
const labelsZone = { ...zone, left: zone.right };
226+
227+
return {
228+
title: {},
229+
dataSets: [{ dataRange: zoneToXc(dataSetZone) }],
230+
type: "sunburst",
231+
legendPosition: "none",
232+
labelRange: zoneToXc(labelsZone),
233+
dataSetsHaveTitle: dataSetsHaveTitle,
234+
};
235+
}

tests/figures/chart/menu_item_insert_chart.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
mockChart,
2525
mountSpreadsheet,
2626
nextTick,
27+
setGrid,
2728
spyModelDispatch,
2829
} from "../../test_helpers/helpers";
2930

@@ -556,6 +557,44 @@ describe("Insert chart menu item", () => {
556557
expect(dispatchSpy).toHaveBeenCalledWith("CREATE_CHART", payload);
557558
});
558559

560+
test("Chart with multiple string columns is a sunburst chart (without headers)", () => {
561+
// prettier-ignore
562+
const grid = {
563+
K1: "Group1", L1: "SubGroup1", M1: "40",
564+
K2: "Group1", L2: "SubGroup2", M2: "20",
565+
K3: "Group2", L3: "SubGroup1", M3: "10",
566+
};
567+
setGrid(model, grid);
568+
setSelection(model, ["K1"]);
569+
insertChart();
570+
const chartId = model.getters.getFigures(model.getters.getActiveSheetId()).at(-1)!.id;
571+
expect(model.getters.getChartDefinition(chartId)).toMatchObject({
572+
type: "sunburst",
573+
dataSets: [{ dataRange: "K1:K3" }, { dataRange: "L1:L3" }],
574+
dataSetsHaveTitle: false,
575+
labelRange: "M1:M3",
576+
});
577+
});
578+
579+
test("Chart with multiple string columns is a sunburst chart (with headers)", () => {
580+
// prettier-ignore
581+
const grid = {
582+
K1: "Header1", L1: "Header2", M1: "Header3",
583+
K2: "Group1", L2: "SubGroup1", M2: "20",
584+
L3: "SubGroup2", M3: "10",
585+
};
586+
setGrid(model, grid);
587+
setSelection(model, ["K1"]);
588+
insertChart();
589+
const chartId = model.getters.getFigures(model.getters.getActiveSheetId()).at(-1)!.id;
590+
expect(model.getters.getChartDefinition(chartId)).toMatchObject({
591+
type: "sunburst",
592+
dataSets: [{ dataRange: "K1:K3" }, { dataRange: "L1:L3" }],
593+
dataSetsHaveTitle: true,
594+
labelRange: "M1:M3",
595+
});
596+
});
597+
559598
test("Chart can be inserted with unbounded ranges", () => {
560599
setSelection(model, ["A1:B100"], { unbounded: true });
561600
insertChart();

0 commit comments

Comments
 (0)