-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathline.ts
125 lines (113 loc) · 3.51 KB
/
line.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { Vector } from '@antv/coord';
import { group } from 'd3-array';
import { isParallel } from '../utils/coordinate';
import { Mark, MarkComponent as MC, SingleMark, Vector2 } from '../runtime';
import { LineMark } from '../spec';
import {
LineShape,
LineHV,
LineVH,
LineHVH,
LineTrail,
LineSmooth,
} from '../shape';
import { MaybeSeries, MaybeGradient } from '../transform';
import {
baseGeometryChannels,
basePostInference,
basePreInference,
tooltip1d,
tooltipXd,
} from './utils';
const shape = {
line: LineShape,
smooth: LineSmooth,
hv: LineHV,
vh: LineVH,
hvh: LineHVH,
trail: LineTrail,
};
export type LineOptions = Omit<LineMark, 'type'>;
const line: Mark = (index, scale, value, coordinate) => {
const { series: S, x: X, y: Y } = value;
const { x, y } = scale;
// Because x and y channel is not strictly required in Line.props,
// it should throw error with empty x or y channels.
if (X === undefined || Y === undefined) {
throw new Error('Missing encode for x or y channel.');
}
// Group data into series.
// There is only one series without specified series encode.
const series = S ? Array.from(group(index, (i) => S[i]).values()) : [index];
const I = series.map((group) => group[0]).filter((i) => i !== undefined);
// A group of data corresponds to one line.
const xoffset = (x?.getBandWidth?.() || 0) / 2;
const yoffset = (y?.getBandWidth?.() || 0) / 2;
const P = Array.from(series, (I) => {
return I.map((i) =>
coordinate.map([+X[i] + xoffset, +Y[i] + yoffset]),
) as Vector2[];
});
return [I, P, series];
};
const parallel: Mark = (index, scale, value, coordinate) => {
// Extract all value for position[number] channels.
const PV = Object.entries(value)
.filter(([key]) => key.startsWith('position'))
.map(([, value]) => value);
// Because position channel is not strictly required in Line.props,
// it should throw error with empty position values.
if (PV.length === 0) {
throw new Error('Missing encode for position channel.');
}
// One data corresponds to one line.
const P = Array.from(index, (i) => {
// Transform high dimension vector to a list of two-dimension vectors.
// [a, b, c] -> [d, e, f, g, h, i]
const vector = PV.map((pv) => +pv[i]);
const vectors = coordinate.map(vector) as Vector;
// Two-dimension vectors are stored in a flat array, so extract them.
// [d, e, f, g, h, i] -> [d, e], [f, g], [h, i]
const points = [];
for (let i = 0; i < vectors.length; i += 2) {
points.push([vectors[i], vectors[i + 1]]);
}
return points;
});
return [index, P];
};
/**
* Convert value for each channel to line shapes.
*/
export const Line: MC<LineOptions> = () => {
return (index, scale, value, coordinate) => {
const mark = isParallel(coordinate) ? parallel : line;
return (mark as SingleMark)(index, scale, value, coordinate);
};
};
Line.props = {
defaultShape: 'line',
defaultLabelShape: 'label',
composite: false,
shape,
channels: [
...baseGeometryChannels({ shapes: Object.keys(shape) }),
{ name: 'x' },
{ name: 'y' },
{ name: 'position', independent: true },
{ name: 'size' },
{ name: 'series', scale: 'band' },
],
preInference: [
...basePreInference(),
// !!!Note This order is very important.
{ type: MaybeGradient },
{ type: MaybeSeries },
],
postInference: [...basePostInference(), ...tooltip1d(), ...tooltipXd()],
interaction: {
shareTooltip: true,
seriesTooltip: true,
crosshairs: true,
},
};