-
Notifications
You must be signed in to change notification settings - Fork 592
/
assemble.ts
182 lines (161 loc) · 5.32 KB
/
assemble.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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import {Axis as VgAxis, AxisEncode, NewSignal, Text} from 'vega';
import {isArray} from 'vega-util';
import {AXIS_PARTS, AXIS_PROPERTY_TYPE, CONDITIONAL_AXIS_PROP_INDEX, isConditionalAxisValue} from '../../axis';
import {POSITION_SCALE_CHANNELS} from '../../channel';
import {defaultTitle, FieldDefBase} from '../../channeldef';
import {Config} from '../../config';
import {getFirstDefined, keys, replaceAll} from '../../util';
import {isSignalRef, VgEncodeChannel, VgValueRef} from '../../vega.schema';
import {Model} from '../model';
import {expression} from '../predicate';
import {AxisComponent, AxisComponentIndex} from './component';
import {isText} from '../../title';
function assembleTitle(title: Text | FieldDefBase<string>[], config: Config): Text {
if (!title) {
return undefined;
}
if (!isText(title)) {
return title.map(fieldDef => defaultTitle(fieldDef, config)).join(', ');
}
return title;
}
function setAxisEncode(
axis: Omit<VgAxis, 'orient' | 'scale'>,
part: keyof AxisEncode,
vgProp: VgEncodeChannel,
vgRef: VgValueRef | readonly VgValueRef[]
) {
axis.encode = axis.encode ?? {};
axis.encode[part] = axis.encode[part] ?? {};
axis.encode[part].update = axis.encode[part].update ?? {};
// TODO: remove as any after https://github.com/prisma/nexus-prisma/issues/291
(axis.encode[part].update[vgProp] as any) = vgRef;
}
export function assembleAxis(
axisCmpt: AxisComponent,
kind: 'main' | 'grid',
config: Config,
opt: {
header: boolean; // whether this is called via a header
} = {header: false}
): VgAxis {
const {orient, scale, labelExpr, title, zindex, ...axis} = axisCmpt.combine();
// Remove properties that are not valid for this kind of axis
keys(axis).forEach(prop => {
const propType = AXIS_PROPERTY_TYPE[prop];
const propValue = axis[prop];
if (propType && propType !== kind && propType !== 'both') {
delete axis[prop];
} else if (isConditionalAxisValue(propValue)) {
const {vgProp, part} = CONDITIONAL_AXIS_PROP_INDEX[prop];
const {condition, value} = propValue;
const vgRef = [
...(isArray(condition) ? condition : [condition]).map(c => {
const {value: v, test} = c;
return {
test: expression(null, test),
value: v
};
}),
{value}
];
setAxisEncode(axis, part, vgProp, vgRef);
delete axis[prop];
}
});
if (kind === 'grid') {
if (!axis.grid) {
return undefined;
}
// Remove unnecessary encode block
if (axis.encode) {
// Only need to keep encode block for grid
const {grid} = axis.encode;
axis.encode = {
...(grid ? {grid} : {})
};
if (keys(axis.encode).length === 0) {
delete axis.encode;
}
}
return {
scale,
orient,
...axis,
domain: false,
labels: false,
// Always set min/maxExtent to 0 to ensure that `config.axis*.minExtent` and `config.axis*.maxExtent`
// would not affect gridAxis
maxExtent: 0,
minExtent: 0,
ticks: false,
zindex: getFirstDefined(zindex, 0) // put grid behind marks by default
};
} else {
// kind === 'main'
if (!opt.header && axisCmpt.mainExtracted) {
// if mainExtracted has been extracted to a separate facet
return undefined;
}
if (labelExpr !== undefined) {
let expr = labelExpr;
if (axis.encode?.labels?.update && isSignalRef(axis.encode.labels.update.text)) {
expr = replaceAll(labelExpr, 'datum.label', axis.encode.labels.update.text.signal);
}
setAxisEncode(axis, 'labels', 'text', {signal: expr});
}
// Remove unnecessary encode block
if (axis.encode) {
for (const part of AXIS_PARTS) {
if (!axisCmpt.hasAxisPart(part)) {
delete axis.encode[part];
}
}
if (keys(axis.encode).length === 0) {
delete axis.encode;
}
}
const titleString = assembleTitle(title, config);
return {
scale,
orient,
grid: false,
...(titleString ? {title: titleString} : {}),
...axis,
zindex: getFirstDefined(zindex, 0) // put axis line above marks by default
};
}
}
/**
* Add axis signals so grid line works correctly
* (Fix https://github.com/vega/vega-lite/issues/4226)
*/
export function assembleAxisSignals(model: Model): NewSignal[] {
const {axes} = model.component;
for (const channel of POSITION_SCALE_CHANNELS) {
if (axes[channel]) {
for (const axis of axes[channel]) {
if (!axis.get('gridScale')) {
// If there is x-axis but no y-scale for gridScale, need to set height/weight so x-axis can draw the grid with the right height. Same for y-axis and width.
const sizeType = channel === 'x' ? 'height' : 'width';
return [
{
name: sizeType,
update: model.getSizeSignalRef(sizeType).signal
}
];
}
}
}
}
return [];
}
export function assembleAxes(axisComponents: AxisComponentIndex, config: Config): VgAxis[] {
const {x = [], y = []} = axisComponents;
return [
...x.map(a => assembleAxis(a, 'grid', config)),
...y.map(a => assembleAxis(a, 'grid', config)),
...x.map(a => assembleAxis(a, 'main', config)),
...y.map(a => assembleAxis(a, 'main', config))
].filter(a => a); // filter undefined
}