Skip to content

Commit 7c0c0a7

Browse files
committed
[IMP] Charts: keep and disable useless dataseries for pyramid chart
Task Description Currently, when creating a pyramid chart with more than two data series, we are removing the unused ones, which lead to a loss of data series when switching back to another chart type. This PR aims to change this behavior, by keeping all the data series but keeping only the first two in the runtime, and showing the redondant series as disabled in the config panel. Related Task Task: 4570622
1 parent 066e8b7 commit 7c0c0a7

File tree

14 files changed

+91
-31
lines changed

14 files changed

+91
-31
lines changed

src/components/selection_input/selection_input.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ interface Props {
4747
onSelectionRemoved?: (index: number) => void;
4848
onSelectionConfirmed?: () => void;
4949
colors?: Color[];
50+
enabled?: boolean[];
5051
}
5152

5253
type SelectionRangeEditMode = "select-range" | "text-edit";
@@ -60,6 +61,7 @@ interface SelectionRange extends Omit<RangeInputValue, "color"> {
6061
isFocused: boolean;
6162
isValidRange: boolean;
6263
color?: Color;
64+
enabled?: boolean;
6365
}
6466
/**
6567
* This component can be used when the user needs to input some
@@ -82,6 +84,7 @@ export class SelectionInput extends Component<Props, SpreadsheetChildEnv> {
8284
onSelectionReordered: { type: Function, optional: true },
8385
onSelectionRemoved: { type: Function, optional: true },
8486
colors: { type: Array, optional: true, default: [] },
87+
enabled: { type: Array, optional: true, default: [] },
8588
};
8689
private state: State = useState({
8790
isMissing: false,
@@ -121,7 +124,8 @@ export class SelectionInput extends Component<Props, SpreadsheetChildEnv> {
121124
SelectionInputStore,
122125
this.props.ranges,
123126
this.props.hasSingleRange || false,
124-
this.props.colors
127+
this.props.colors,
128+
this.props.enabled
125129
);
126130
onWillUpdateProps((nextProps) => {
127131
if (nextProps.ranges.join() !== this.store.selectionInputValues.join()) {
@@ -139,6 +143,12 @@ export class SelectionInput extends Component<Props, SpreadsheetChildEnv> {
139143
) {
140144
this.store.updateColors(nextProps.colors || []);
141145
}
146+
if (
147+
nextProps.enabled?.join() !== this.props.enabled?.join() &&
148+
nextProps.enabled?.join() !== this.store.enabled.join()
149+
) {
150+
this.store.updateEnabled(nextProps.enabled || []);
151+
}
142152
});
143153
}
144154

@@ -179,12 +189,19 @@ export class SelectionInput extends Component<Props, SpreadsheetChildEnv> {
179189
}
180190

181191
getColor(range: SelectionRange) {
192+
if (!this.isEnabled(range)) {
193+
return "#fff";
194+
}
182195
if (!range.color) {
183196
return "";
184197
}
185198
return cssPropertiesToCss({ color: range.color });
186199
}
187200

201+
isEnabled(range: SelectionRange): boolean {
202+
return range.enabled ?? true;
203+
}
204+
188205
private triggerChange() {
189206
const ranges = this.store.selectionInputValues;
190207
this.props.onSelectionChanged?.(ranges);

src/components/selection_input/selection_input.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
t-att-class="{
3030
'o-focused' : range.isFocused,
3131
'o-invalid border-danger position-relative': isInvalid || !range.isValidRange,
32-
'text-decoration-underline': range.xc and range.isFocused and state.mode === 'select-range'
32+
'text-decoration-underline': range.xc and range.isFocused and state.mode === 'select-range',
33+
'o-disabled': !isEnabled(range)
3334
}"
3435
t-ref="{{range.isFocused ? 'focusedInput' : 'unfocusedInput' + range_index}}"
3536
/>

src/components/selection_input/selection_input_store.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export class SelectionInputStore extends SpreadsheetStore {
3030
"reset",
3131
"confirm",
3232
"updateColors",
33+
"updateEnabled",
3334
] as const;
3435
private ranges: RangeInputValue[] = [];
3536
focusedRangeIndex: number | null = null;
@@ -41,7 +42,8 @@ export class SelectionInputStore extends SpreadsheetStore {
4142
get: Get,
4243
private initialRanges: string[] = [],
4344
private readonly inputHasSingleRange: boolean = false,
44-
public colors: Color[] = []
45+
public colors: Color[] = [],
46+
public enabled: boolean[] = []
4547
) {
4648
super(get);
4749
if (inputHasSingleRange && initialRanges.length > 1) {
@@ -173,6 +175,10 @@ export class SelectionInputStore extends SpreadsheetStore {
173175
}));
174176
}
175177

178+
updateEnabled(enabled: boolean[]) {
179+
this.enabled = enabled;
180+
}
181+
176182
confirm() {
177183
for (const range of this.selectionInputs) {
178184
if (range.xc === "") {
@@ -221,6 +227,7 @@ export class SelectionInputStore extends SpreadsheetStore {
221227
: null,
222228
isFocused: this.hasMainFocus && this.focusedRangeIndex === index,
223229
isValidRange: input.xc === "" || this.getters.isRangeValid(input.xc),
230+
enabled: this.enabled?.[index],
224231
})
225232
);
226233
}

src/components/side_panel/chart/building_blocks/data_series/data_series.ts

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export class ChartDataSeries extends Component<Props, SpreadsheetChildEnv> {
3333
return this.props.ranges.map((r) => r.backgroundColor);
3434
}
3535

36+
get enabled(): boolean[] {
37+
return this.props.ranges.map((r) => !r.ignored);
38+
}
39+
3640
get title() {
3741
return this.props.hasSingleRange ? _t("Data range") : _t("Data series");
3842
}

src/components/side_panel/chart/building_blocks/data_series/data_series.xml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
onSelectionReordered="props.onSelectionReordered"
1111
onSelectionRemoved="props.onSelectionRemoved"
1212
colors="colors"
13+
enabled="enabled"
1314
/>
1415
</Section>
1516
</t>

src/components/side_panel/chart/building_blocks/generic_side_panel/config_panel.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,12 @@ export class GenericChartConfigPanel extends Component<Props, SpreadsheetChildEn
128128
{ dataSets: this.dataSets },
129129
this.dataSets.length
130130
);
131+
const ignored = this.dataSets.map((ds) => ds.ignored);
131132
const colors = this.dataSets.map((ds) => colorGenerator.next());
132-
this.dataSets = indexes.map((i) => ({
133-
backgroundColor: colors[i],
134-
...this.dataSets[i],
133+
this.dataSets = indexes.map((index, i) => ({
134+
backgroundColor: colors[index],
135+
...this.dataSets[index],
136+
ignored: ignored[i],
135137
}));
136138
this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
137139
dataSets: this.dataSets,
@@ -144,12 +146,19 @@ export class GenericChartConfigPanel extends Component<Props, SpreadsheetChildEn
144146
this.dataSets.length
145147
);
146148
const colors = this.dataSets.map((ds) => colorGenerator.next());
149+
const ignored = this.dataSets.map((ds) => ds.ignored);
147150
this.dataSets = this.dataSets
148151
.map((ds, i) => ({
149152
backgroundColor: colors[i],
150153
...ds,
151154
}))
152155
.filter((_, i) => i !== index);
156+
for (let i = 0; i < this.dataSets.length; i++) {
157+
this.dataSets[i] = {
158+
...this.dataSets[i],
159+
ignored: ignored[i],
160+
};
161+
}
153162
this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
154163
dataSets: this.dataSets,
155164
});

src/components/side_panel/chart/geo_chart_panel/geo_chart_config_panel.ts

+1-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { spreadRange } from "../../../../helpers";
21
import { GenericChartConfigPanel } from "../building_blocks/generic_side_panel/config_panel";
32
import { GeoChartRegionSelectSection } from "./geo_chart_region_select_section";
43

@@ -7,14 +6,7 @@ export class GeoChartConfigPanel extends GenericChartConfigPanel {
76
static components = { ...GenericChartConfigPanel.components, GeoChartRegionSelectSection };
87

98
get dataRanges() {
10-
return this.getDataSeriesRanges().slice(0, 1);
11-
}
12-
13-
onDataSeriesConfirmed() {
14-
this.dataSets = spreadRange(this.env.model.getters, this.dataSets).slice(0, 1);
15-
this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
16-
dataSets: this.dataSets,
17-
});
9+
return this.getDataSeriesRanges().map((ds, i) => ({ ...ds, ignored: i > 0 }));
1810
}
1911

2012
getLabelRangeOptions() {

src/components/side_panel/chart/geo_chart_panel/geo_chart_config_panel.xml

+4-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99

1010
<ChartDataSeries
1111
ranges="dataRanges"
12-
onSelectionChanged="(ranges) => this.onDataSeriesRangesChanged(ranges)"
13-
onSelectionConfirmed="() => this.onDataSeriesConfirmed()"
14-
hasSingleRange="true"
12+
onSelectionChanged.bind="onDataSeriesRangesChanged"
13+
onSelectionConfirmed.bind="onDataSeriesConfirmed"
14+
onSelectionReordered.bind="onDataSeriesReordered"
15+
onSelectionRemoved.bind="onDataSeriesRemoved"
1516
/>
1617
<ChartLabelRange
1718
range="this.getLabelRange()"

src/helpers/figures/charts/geo_chart.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export class GeoChart extends AbstractChart {
109109
range.push({
110110
...this.dataSetDesign?.[i],
111111
dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
112+
ignored: undefined,
112113
});
113114
}
114115
return {
@@ -154,6 +155,7 @@ export class GeoChart extends AbstractChart {
154155
ranges.push({
155156
...this.dataSetDesign?.[i],
156157
dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
158+
ignored: i > 0,
157159
});
158160
}
159161
return {
@@ -193,7 +195,12 @@ export class GeoChart extends AbstractChart {
193195

194196
export function createGeoChartRuntime(chart: GeoChart, getters: Getters): GeoChartRuntime {
195197
const definition = chart.getDefinition();
196-
const chartData = getGeoChartData(definition, chart.dataSets, chart.labelRange, getters);
198+
const chartData = getGeoChartData(
199+
definition,
200+
chart.dataSets.slice(0, 1),
201+
chart.labelRange,
202+
getters
203+
);
197204

198205
const config: ChartConfiguration = {
199206
type: "choropleth",

src/helpers/figures/charts/pyramid_chart.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class PyramidChart extends AbstractChart {
6666
definition.dataSets,
6767
sheetId,
6868
definition.dataSetsHaveTitle
69-
).slice(0, 2);
69+
);
7070
this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
7171
this.background = definition.background;
7272
this.legendPosition = definition.legendPosition;
@@ -114,6 +114,7 @@ export class PyramidChart extends AbstractChart {
114114
range.push({
115115
...this.dataSetDesign?.[i],
116116
dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
117+
ignored: undefined,
117118
});
118119
}
119120
return {
@@ -159,6 +160,7 @@ export class PyramidChart extends AbstractChart {
159160
ranges.push({
160161
...this.dataSetDesign?.[i],
161162
dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
163+
ignored: i > 1,
162164
});
163165
}
164166
return {
@@ -203,7 +205,12 @@ export function createPyramidChartRuntime(
203205
getters: Getters
204206
): PyramidChartRuntime {
205207
const definition = chart.getDefinition();
206-
const chartData = getPyramidChartData(definition, chart.dataSets, chart.labelRange, getters);
208+
const chartData = getPyramidChartData(
209+
definition,
210+
chart.dataSets.slice(0, 2),
211+
chart.labelRange,
212+
getters
213+
);
207214

208215
const config: ChartConfiguration = {
209216
type: "bar",

src/types/chart/chart.ts

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export interface DatasetDesign {
7979
readonly backgroundColor?: string;
8080
readonly yAxisId?: string;
8181
readonly label?: string;
82+
readonly ignored?: boolean;
8283
}
8384

8485
export interface AxisDesign {

tests/figures/chart/geochart/geo_chart_panel_component.test.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,24 @@ describe("Geo chart side panel", () => {
5353
expect(".o-geo-region select").toHaveValue("usa");
5454
});
5555

56-
test("Can only select a single data range", async () => {
56+
test("Only one data range is enabled", async () => {
5757
createGeoChart(model, {
5858
dataSets: [{ dataRange: "A1:A3" }, { dataRange: "B1:B3" }],
5959
});
6060
await openChartConfigSidePanel(model, env, chartId);
61-
expect(".o-data-series input").toHaveCount(1);
62-
expect(".o-data-series input").toHaveValue("A1:A3");
61+
expect(".o-data-series input").toHaveCount(2);
6362

6463
await setInputValueAndTrigger(".o-data-series input", "A1:D3");
6564
await simulateClick(".o-data-series .o-selection-ok");
6665

6766
expect(".o-data-series input").toHaveValue("A1:A3");
68-
expect(getGeoChartDefinition(chartId)?.dataSets).toMatchObject([{ dataRange: "A1:A3" }]);
67+
expect(getGeoChartDefinition(chartId)?.dataSets).toMatchObject([
68+
{ dataRange: "A1:A3", ignored: false },
69+
{ dataRange: "B1:B3", ignored: true },
70+
{ dataRange: "C1:C3", ignored: true },
71+
{ dataRange: "D1:D3", ignored: true },
72+
{ dataRange: "B1:B3", ignored: true },
73+
]);
6974
});
7075

7176
test("Can change the displayed region", async () => {

tests/figures/chart/pyramid_chart/pyramid_chart_component.test.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe("Pyramid chart side panel", () => {
2020
({ fixture, env } = await mountComponentWithPortalTarget(SidePanel, { model }));
2121
});
2222

23-
test("Only first 2 ranges are kept when changing the selection input", async () => {
23+
test("Only first 2 ranges are enabled when changing the selection input", async () => {
2424
createChart(model, { type: "pyramid", dataSets: [] }, "id");
2525
await openChartConfigSidePanel(model, env, "id");
2626

@@ -30,13 +30,17 @@ describe("Pyramid chart side panel", () => {
3030
await simulateClick(".o-data-series .o-selection-ok");
3131

3232
expect(getPyramidDefinition("id").dataSets).toEqual([
33-
{ dataRange: "A1:A5" },
34-
{ dataRange: "B1:B5" },
33+
{ dataRange: "A1:A5", ignored: false },
34+
{ dataRange: "B1:B5", ignored: false },
35+
{ dataRange: "C1:C5", ignored: true },
36+
{ dataRange: "D1:D5", ignored: true },
3537
]);
3638

3739
const inputs = fixture.querySelectorAll<HTMLInputElement>(".o-chart .o-data-series input");
38-
expect(inputs).toHaveLength(2);
40+
expect(inputs).toHaveLength(4);
3941
expect(inputs[0].value).toBe("A1:A5");
4042
expect(inputs[1].value).toBe("B1:B5");
43+
expect(inputs[2].value).toBe("C1:C5");
44+
expect(inputs[3].value).toBe("D1:D5");
4145
});
4246
});

tests/figures/chart/pyramid_chart/pyramid_chart_plugin.test.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,15 @@ describe("population pyramid chart", () => {
4848
model = new Model();
4949
});
5050

51-
test("We only keep the first two datasets", () => {
51+
test("We only keep the first two datasets and ignore the others", () => {
5252
const dataSets = [{ dataRange: "A1" }, { dataRange: "A2" }, { dataRange: "A3" }];
5353
createChart(model, { type: "pyramid", dataSets }, "id");
5454
const definition = model.getters.getChartDefinition("id") as PyramidChartDefinition;
55-
expect(definition.dataSets).toEqual([{ dataRange: "A1" }, { dataRange: "A2" }]);
55+
expect(definition.dataSets).toEqual([
56+
{ dataRange: "A1", ignored: false },
57+
{ dataRange: "A2", ignored: false },
58+
{ dataRange: "A3", ignored: true },
59+
]);
5660
});
5761

5862
test("Runtime is a stacked bar chart, with the second dataset converted to negative values", () => {

0 commit comments

Comments
 (0)