Skip to content

Commit 25fbe90

Browse files
authored
fix(dualaxes): configure legend selected (#2784)
* test(dualaxes): add tests for legend selection * fix(dualaxes): configure left and right views' legend
1 parent 93000e5 commit 25fbe90

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

__tests__/bugs/issue-2783-spec.ts

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { Chart, Element } from '@antv/g2';
2+
import { get, isArray } from '@antv/util';
3+
import { DualAxes, DualAxesOptions } from '../../src';
4+
import { LEFT_AXES_VIEW, RIGHT_AXES_VIEW } from '../../src/plots/dual-axes/constant';
5+
import { getAllElementsRecursively } from '../../src/utils';
6+
import { transformData, uvBillData } from '../data/pv-uv';
7+
import { createDiv, removeDom } from '../utils/dom';
8+
9+
const views = {
10+
[LEFT_AXES_VIEW]: {
11+
yField: 'value',
12+
seriesField: 'type',
13+
data: uvBillData,
14+
},
15+
[RIGHT_AXES_VIEW]: {
16+
yField: 'count',
17+
seriesField: 'name',
18+
data: transformData,
19+
},
20+
};
21+
22+
const config: DualAxesOptions = {
23+
data: [views[LEFT_AXES_VIEW].data, views[RIGHT_AXES_VIEW].data],
24+
xField: 'time',
25+
yField: [views[LEFT_AXES_VIEW].yField, views[RIGHT_AXES_VIEW].yField],
26+
geometryOptions: [
27+
{
28+
geometry: 'line',
29+
seriesField: views[LEFT_AXES_VIEW].seriesField,
30+
lineStyle: {
31+
lineWidth: 3,
32+
lineDash: [5, 5],
33+
},
34+
smooth: true,
35+
},
36+
{
37+
geometry: 'line',
38+
seriesField: views[RIGHT_AXES_VIEW].seriesField,
39+
point: {},
40+
},
41+
],
42+
};
43+
44+
const valuesBySeriesField = Object.values(views).map(({ data, seriesField }) => [
45+
seriesField,
46+
getUniqueValues(data, seriesField),
47+
]);
48+
const seriesValues: Record<string, any[]> = Object.fromEntries(valuesBySeriesField);
49+
50+
function getUniqueValues(data: any[], field: string): string[] {
51+
return Array.from(new Set(data.map((d) => d[field])));
52+
}
53+
54+
function applySelection(target: Record<string, boolean>, source: Record<string, boolean>) {
55+
return { ...target, ...source };
56+
}
57+
58+
function getElementValue(element: Element, field: string) {
59+
const model = element.getModel();
60+
const record = model.data;
61+
return isArray(record) ? record[0][field] : record[field];
62+
}
63+
64+
function getElementsByField(elements: Element[], field: string, value: any) {
65+
return elements.filter((el) => getElementValue(el, field) === value);
66+
}
67+
68+
function testElements(chart: Chart, selection: Record<string, boolean>) {
69+
const elements = getAllElementsRecursively(chart);
70+
Object.values(views).forEach(({ seriesField }) =>
71+
seriesValues[seriesField].forEach((value) =>
72+
test(`elements for ${value} ${selection[value] ? 'should' : "shouldn't"} be displayed`, () => {
73+
const seriesFieldElements = getElementsByField(elements, seriesField, value);
74+
const elementsDisplayed = seriesFieldElements.length > 0;
75+
expect(elementsDisplayed).toBe(selection[value]);
76+
})
77+
)
78+
);
79+
}
80+
81+
function testItems(chart: Chart, selection: Record<string, boolean>): void {
82+
const legendItems = get(chart.getController('legend'), 'option.items', []);
83+
legendItems
84+
.map(({ name, unchecked }) => ({ name, unchecked: !unchecked }))
85+
.forEach(({ name, unchecked }) =>
86+
test(`item ${name} ${selection[name] ? 'should' : "shouldn't"} be selected`, () =>
87+
expect(unchecked).toBe(selection[name]))
88+
);
89+
}
90+
91+
function testLegendSelected(chart: Chart, selection: Record<string, boolean>) {
92+
testItems(chart, selection);
93+
testElements(chart, selection);
94+
}
95+
96+
describe('#2783', () => {
97+
describe.each([
98+
['uncheck bill item', {}, { bill: false }],
99+
['check unchecked bill item', { bill: false }, { bill: true }],
100+
['uncheck checked item a', { a: true }, { a: false }],
101+
['uncheck all items', {}, { uv: false, bill: false, a: false, b: false, c: false }],
102+
['check all items', { uv: false, bill: false, a: false, b: false, c: false }, {}],
103+
])('DualAxes: %s', (_, initial, updated) => {
104+
const legend = { ...config.legend, selected: initial };
105+
const container = createDiv();
106+
const dualAxes = new DualAxes(container, { ...config, legend });
107+
108+
const defaultSelection = Object.values(views)
109+
.flatMap(({ data, seriesField }) => getUniqueValues(data, seriesField))
110+
.reduce((acc, v) => ({ ...acc, ...{ [v]: true } }), {});
111+
112+
const initialSelection = applySelection(defaultSelection, initial);
113+
114+
describe(`create with legend.selected=${JSON.stringify(initial)}`, () => {
115+
dualAxes.render();
116+
testLegendSelected(dualAxes.chart, initialSelection);
117+
});
118+
119+
describe(`update legend.selected=${JSON.stringify(updated)}`, () => {
120+
dualAxes.update({
121+
legend: {
122+
...legend,
123+
selected: updated,
124+
},
125+
});
126+
127+
const updatedSelection = applySelection(initialSelection, updated);
128+
testLegendSelected(dualAxes.chart, updatedSelection);
129+
});
130+
131+
afterAll(() => {
132+
dualAxes.destroy();
133+
removeDom(container);
134+
});
135+
});
136+
});

src/plots/dual-axes/adaptor.ts

+7
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,13 @@ export function legend(params: Params<DualAxesOptions>): Params<DualAxesOptions>
380380
);
381381
});
382382

383+
if (geometryOptions[0].seriesField) {
384+
leftView.legend(geometryOptions[0].seriesField, legend);
385+
}
386+
if (geometryOptions[1].seriesField) {
387+
rightView.legend(geometryOptions[1].seriesField, legend);
388+
}
389+
383390
// 自定义图例交互
384391
chart.on('legend-item:click', (evt) => {
385392
const delegateObject = get(evt, 'gEvent.delegateObject', {});

0 commit comments

Comments
 (0)