-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstackY.ts
116 lines (105 loc) · 3.32 KB
/
stackY.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
import { deepMix } from '@antv/util';
import { TransformComponent as TC } from '../runtime';
import { StackYTransform } from '../spec';
import {
column,
columnOf,
inferredColumn,
maybeColumnOf,
} from './utils/helper';
import { normalizeComparator, createGroups, applyOrder } from './utils/order';
export type StackYOptions = Omit<StackYTransform, 'type'>;
/**
* The stack transform group marks into series by color channel,
* and then produce new y channel for each series by specified order,
* say to form vertical "stacks" by specified channels.
*/
export const StackY: TC<StackYOptions> = (options = {}) => {
const {
groupBy = 'x',
orderBy = null,
reverse = false,
y: fromY = 'y',
y1: fromY1 = 'y1',
series = true,
} = options;
return (I, mark) => {
const { data, encode, style = {} } = mark;
const [Y, fy] = columnOf(encode, 'y');
const [Y1, fy1] = columnOf(encode, 'y1');
const [S] = series
? maybeColumnOf(encode, 'series', 'color')
: columnOf(encode, 'color');
// Create groups and apply specified order for each group.
const groups = createGroups(groupBy, I, mark);
const createComparator = normalizeComparator(orderBy) ?? (() => null);
const comparator = createComparator(data, Y, S);
if (comparator) applyOrder(groups, comparator);
// Stack y channels to produce new y and y1 channel.
const newY = new Array(I.length);
const newY1 = new Array(I.length);
const TY = new Array(I.length);
const F = [];
const L = [];
for (const G of groups) {
if (reverse) G.reverse();
// For range interval with specified y and y1.
const start = Y1 ? +Y1[G[0]] : 0;
// Split positive indices of Y and negative Y.
const PG = [];
const NG = [];
for (const i of G) {
const y = (TY[i] = +Y[i] - start);
if (y < 0) NG.push(i);
else if (y >= 0) PG.push(i);
}
// Store the first and last layer.
const FG = PG.length > 0 ? PG : NG;
const LG = NG.length > 0 ? NG : PG;
let i = PG.length - 1;
let j = 0;
// Find the last non-zero index.
while (i > 0 && Y[FG[i]] === 0) i--;
// Find the first non-zero index.
while (j < LG.length - 1 && Y[LG[j]] === 0) j++;
F.push(FG[i]);
L.push(LG[j]);
// Stack negative y in reverse order.
let ny = start;
for (const i of NG.reverse()) {
const y = TY[i];
ny = newY[i] = (newY1[i] = ny) + y;
}
// Stack positive y in input order.
let py = start;
for (const i of PG) {
const y = TY[i];
if (y > 0) py = newY[i] = (newY1[i] = py) + y;
else newY[i] = newY1[i] = py;
}
}
// Only set top radius for the first layer,
// and set bottom radius for the last layer.
const FS = new Set(F);
const LS = new Set(L);
// Choose new y or y1 channel as the new y channel.
const V = fromY === 'y' ? newY : newY1;
const V1 = fromY1 === 'y' ? newY : newY1;
return [
I,
deepMix({}, mark, {
encode: {
y0: inferredColumn(Y, fy), // Store original Y.
y: column(V, fy),
y1: column(V1, fy1),
},
style: {
first: (_, i) => FS.has(i),
last: (_, i) => LS.has(i),
...style,
},
}),
];
};
};
StackY.props = {};