Skip to content

Commit 649ec99

Browse files
moayuisuda林舸
andauthored
chore: export some useful util functions (#6648)
Co-authored-by: 林舸 <anhaohui.ahh@antgroup.com>
1 parent c4105c1 commit 649ec99

File tree

6 files changed

+237
-52
lines changed

6 files changed

+237
-52
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { Band, Constant } from '@antv/scale';
2+
import { groupNameOf, dataOf, seriesOf } from '../../../src/utils/helper';
3+
import { Chart } from '../../../src';
4+
import { createNodeGCanvas } from '../../integration/utils/createNodeGCanvas';
5+
import { G2Element } from '../../../src/utils/selection';
6+
7+
describe('groupNameOf', () => {
8+
it('should handle band series scale', () => {
9+
const seriesScale = new Band();
10+
seriesScale.update({ domain: ['A', 'B', 'C', 'D'], range: [0, 1] });
11+
const scale = { series: seriesScale };
12+
const datum = { series: 0.25 };
13+
expect(groupNameOf(scale, datum)).toBe('B');
14+
});
15+
16+
it('should return null for constant scale', () => {
17+
const seriesScale = new Constant();
18+
seriesScale.update({ domain: [1], range: [1] });
19+
const scale = { series: seriesScale };
20+
const datum = { series: 1 };
21+
expect(groupNameOf(scale, datum)).toBeNull();
22+
});
23+
});
24+
25+
describe('seriesOf', () => {
26+
const chart = new Chart({
27+
canvas: createNodeGCanvas(640, 480),
28+
});
29+
30+
it('should return null for non-series mark', async () => {
31+
const data = [
32+
{ x: 1, y: 10 },
33+
{ x: 2, y: 20 },
34+
];
35+
chart.options({
36+
type: 'interval',
37+
data,
38+
encode: {
39+
x: 'x',
40+
y: 'y',
41+
},
42+
});
43+
44+
await chart.render();
45+
46+
const rects = chart
47+
.getContext()
48+
?.canvas?.document.getElementsByClassName('element');
49+
const firstRect = rects?.[0] as G2Element;
50+
const name = seriesOf(firstRect);
51+
52+
expect(name).toBe(null);
53+
});
54+
55+
it('should get series value from grouped data', async () => {
56+
const data = [
57+
{ year: '2020', value: 10, type: 'A' },
58+
{ year: '2020', value: 20, type: 'B' },
59+
{ year: '2021', value: 15, type: 'A' },
60+
{ year: '2021', value: 25, type: 'B' },
61+
];
62+
63+
chart.options({
64+
type: 'interval',
65+
data,
66+
encode: {
67+
x: 'year',
68+
y: 'value',
69+
color: 'type',
70+
series: 'type',
71+
},
72+
});
73+
74+
await chart.render();
75+
76+
const rects = chart
77+
.getContext()
78+
?.canvas?.document.getElementsByClassName('element');
79+
const firstRect = rects?.[1] as G2Element;
80+
const name = seriesOf(firstRect);
81+
82+
expect(name).toBe('B');
83+
});
84+
});
85+
86+
describe('dataOf', () => {
87+
const chart = new Chart({
88+
canvas: createNodeGCanvas(640, 480),
89+
});
90+
91+
it('should get single data point from interval element', async () => {
92+
const data = [
93+
{ x: 1, y: 10 },
94+
{ x: 2, y: 20 },
95+
];
96+
chart.options({
97+
type: 'interval',
98+
data,
99+
encode: {
100+
x: 'x',
101+
y: 'y',
102+
},
103+
});
104+
105+
await chart.render();
106+
107+
const rects = chart
108+
.getContext()
109+
?.canvas?.document.getElementsByClassName('element');
110+
const firstRect = rects?.[0] as G2Element;
111+
const originalData = dataOf(firstRect);
112+
113+
expect(originalData).toEqual(data[0]);
114+
});
115+
116+
it('should get series data from grouped interval element', async () => {
117+
const data = [
118+
{ year: '2020', value: 10, type: 'A' },
119+
{ year: '2020', value: 20, type: 'B' },
120+
{ year: '2021', value: 15, type: 'A' },
121+
{ year: '2021', value: 25, type: 'B' },
122+
];
123+
124+
chart.options({
125+
type: 'interval',
126+
data,
127+
encode: {
128+
x: 'year',
129+
y: 'value',
130+
color: 'type',
131+
},
132+
});
133+
134+
await chart.render();
135+
136+
const rects = chart
137+
.getContext()
138+
?.canvas?.document.getElementsByClassName('element');
139+
const firstRect = rects?.[0] as G2Element;
140+
const seriesData = dataOf(firstRect);
141+
142+
expect(seriesData).toEqual({ year: '2020', value: 10, type: 'A' });
143+
});
144+
});

src/api/runtime.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ export class Runtime<Spec extends G2Spec = G2Spec> extends CompositionNode {
7575
if (!this._context.canvas) this._createCanvas();
7676
this._bindAutoFit();
7777
this._rendering = true;
78+
79+
// @fixme The cancel render is not marked, which will cause additional rendered event.
80+
// @ref src/runtime/render.ts
7881
const finished = new Promise<Runtime<Spec>>((resolve, reject) =>
7982
render(
8083
this._computedOptions(),

src/exports.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ export type {
3737
Channel,
3838
} from './runtime';
3939
export { select, Selection } from './utils/selection';
40+
export { dataOf, seriesOf } from './utils/helper';
41+
export { selectG2Elements, selectPlotArea } from './interaction/utils';
42+
4043
export * from './transform';
4144
export { LinearAxis } from './component/axis';
4245
export type { AxisOptions } from './component/axis';

src/interaction/event.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,7 @@
11
import { ChartEvent } from '../utils/event';
2+
import { dataOf } from '../utils/helper';
23
import { maybeRoot } from './utils';
34

4-
export function dataOf(element, view) {
5-
const { __data__: datum } = element;
6-
const { markKey, index, seriesIndex } = datum;
7-
const { markState } = view;
8-
const selectedMark: any = Array.from(markState.keys()).find(
9-
(mark) => (mark as any).key === markKey,
10-
);
11-
if (!selectedMark) return;
12-
if (seriesIndex) {
13-
return seriesIndex.map((i) => selectedMark.data[i]);
14-
}
15-
return selectedMark.data[index];
16-
}
17-
185
// For extended component
196
function maybeComponentRoot(node) {
207
return maybeRoot(node, (node) => node.className === 'component');
@@ -63,7 +50,7 @@ function bubblesEvent(eventType, view, emitter, predicate = (event) => true) {
6350
nativeEvent: true,
6451
};
6552
if (elementType === 'element') {
66-
e1['data'] = { data: dataOf(root, view) };
53+
e1['data'] = { data: dataOf(root) };
6754
emitter.emit(`element:${eventType}`, e1);
6855
emitter.emit(`${markType}:${eventType}`, e1);
6956
} else if (elementType === 'label') {

src/interaction/tooltip.ts

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { Circle, DisplayObject, IElement, Line } from '@antv/g';
22
import { sort, group, mean, bisector, minIndex } from '@antv/vendor/d3-array';
33
import { deepMix, lowerFirst, throttle } from '@antv/util';
44
import { Tooltip as TooltipComponent } from '@antv/component';
5-
import { Constant, Band } from '@antv/scale';
6-
import { defined, subObject } from '../utils/helper';
5+
import { defined, groupNameOf, subObject, dataOf } from '../utils/helper';
76
import { isTranspose, isPolar } from '../utils/coordinate';
87
import { angle, sub, dist } from '../utils/vector';
98
import { invert } from '../utils/scale';
@@ -20,7 +19,6 @@ import {
2019
bboxOf,
2120
maybeRoot,
2221
} from './utils';
23-
import { dataOf } from './event';
2422

2523
function getContainer(
2624
group: IElement,
@@ -209,39 +207,6 @@ function singleItem(element) {
209207
};
210208
}
211209

212-
function groupNameOf(scale, datum) {
213-
const { color: scaleColor, series: scaleSeries, facet = false } = scale;
214-
const { color, series } = datum;
215-
const invertAble = (scale) => {
216-
return (
217-
scale &&
218-
scale.invert &&
219-
!(scale instanceof Band) &&
220-
!(scale instanceof Constant)
221-
);
222-
};
223-
// For non constant color channel.
224-
if (invertAble(scaleSeries)) {
225-
const cloned = scaleSeries.clone();
226-
return cloned.invert(series);
227-
}
228-
if (
229-
series &&
230-
scaleSeries instanceof Band &&
231-
scaleSeries.invert(series) !== color &&
232-
!facet
233-
) {
234-
return scaleSeries.invert(series);
235-
}
236-
if (invertAble(scaleColor)) {
237-
const name = scaleColor.invert(color);
238-
// For threshold scale.
239-
if (Array.isArray(name)) return null;
240-
return name;
241-
}
242-
return null;
243-
}
244-
245210
function itemColorOf(element) {
246211
const fill = element.getAttribute('fill');
247212
const stroke = element.getAttribute('stroke');
@@ -1103,7 +1068,7 @@ export function tooltip(
11031068
nativeEvent: true,
11041069
data: {
11051070
...data,
1106-
data: dataOf(element, view),
1071+
data: dataOf(element),
11071072
},
11081073
});
11091074
},

src/utils/helper.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,88 @@
11
import { DisplayObject } from '@antv/g';
22
import { lowerFirst, upperFirst, isPlainObject } from '@antv/util';
3+
import { Band, Base, Constant } from '@antv/scale';
4+
import { G2Element } from './selection';
5+
6+
/**
7+
* @description Get element's ancestor view node.
8+
* @param elemenet G2 element.
9+
* @returns Element's ancestor view node.
10+
*/
11+
export function getViewFromElement(element: G2Element) {
12+
let current = element as G2Element;
13+
while (current) {
14+
if (current.attributes?.class === 'view') return current;
15+
current = current.parentNode as G2Element;
16+
}
17+
return null;
18+
}
19+
20+
/**
21+
* @description Get element's original data.
22+
* @param elemenet G2 element.
23+
* @returns The original data of the element.
24+
*/
25+
export function dataOf(element: G2Element) {
26+
const view = getViewFromElement(element);
27+
const datum = element.__data__;
28+
const { markKey, index, seriesIndex } = datum;
29+
const { markState } = view.__data__;
30+
const selectedMark: any = Array.from(markState.keys()).find(
31+
(mark) => (mark as any).key === markKey,
32+
);
33+
if (!selectedMark) return;
34+
if (seriesIndex) {
35+
return seriesIndex.map((i) => selectedMark.data[i]);
36+
}
37+
return selectedMark.data[index];
38+
}
39+
40+
/**
41+
* @description Get element's series name.
42+
* @param elemenet G2 element.
43+
* @returns The series name of the element.
44+
*/
45+
export function seriesOf(elemenet: G2Element): string {
46+
const viewData = getViewFromElement(elemenet).__data__;
47+
const { scale } = viewData;
48+
return groupNameOf(scale, elemenet.__data__);
49+
}
50+
51+
/**
52+
* Get group name with view's scale and element's datum.
53+
*/
54+
export function groupNameOf(scale: Record<string, Base<any>>, datum) {
55+
const { color: scaleColor, series: scaleSeries, facet = false } = scale;
56+
const { color, series } = datum;
57+
const invertAble = (scale) => {
58+
return (
59+
scale &&
60+
scale.invert &&
61+
!(scale instanceof Band) &&
62+
!(scale instanceof Constant)
63+
);
64+
};
65+
// For non constant color channel.
66+
if (invertAble(scaleSeries)) {
67+
const cloned = scaleSeries.clone();
68+
return cloned.invert(series);
69+
}
70+
if (
71+
series &&
72+
scaleSeries instanceof Band &&
73+
scaleSeries.invert(series) !== color &&
74+
!facet
75+
) {
76+
return scaleSeries.invert(series);
77+
}
78+
if (invertAble(scaleColor)) {
79+
const name = scaleColor.invert(color);
80+
// For threshold scale.
81+
if (Array.isArray(name)) return null;
82+
return name;
83+
}
84+
return null;
85+
}
386

487
export function identity<T>(x: T): T {
588
return x;

0 commit comments

Comments
 (0)