forked from vega/vega-lite
/
base.ts
359 lines (314 loc) · 13.2 KB
/
base.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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
import {Color, Cursor, SignalRef, Text} from 'vega';
import {isNumber, isObject} from 'vega-util';
import {NormalizedSpec} from '.';
import {Data} from '../data';
import {ExprRef} from '../expr';
import {MarkConfig} from '../mark';
import {Resolve} from '../resolve';
import {TitleParams} from '../title';
import {Transform} from '../transform';
import {Flag, keys} from '../util';
import {LayoutAlign, RowCol} from '../vega.schema';
import {isConcatSpec, isVConcatSpec} from './concat';
import {isFacetMapping, isFacetSpec} from './facet';
export type {TopLevel} from './toplevel';
/**
* Common properties for all types of specification
*/
export interface BaseSpec {
/**
* Title for the plot.
*/
title?: Text | TitleParams<ExprRef | SignalRef>;
/**
* Name of the visualization for later reference.
*/
name?: string;
/**
* Description of this mark for commenting purpose.
*/
description?: string;
/**
* An object describing the data source. Set to `null` to ignore the parent's data source. If no data is set, it is derived from the parent.
*/
data?: Data | null;
/**
* An array of data transformations such as filter and new field calculation.
*/
transform?: Transform[];
}
export interface DataMixins {
/**
* An object describing the data source.
*/
data: Data;
}
export type StepFor = 'position' | 'offset';
export interface Step {
/**
* The size (width/height) per discrete step.
*/
step: number;
/**
* Whether to apply the step to position scale or offset scale when there are both `x` and `xOffset` or both `y` and `yOffset` encodings.
*/
for?: StepFor;
}
export function getStepFor({step, offsetIsDiscrete}: {step: Step; offsetIsDiscrete: boolean}): StepFor {
if (offsetIsDiscrete) {
return step.for ?? 'offset';
} else {
return 'position';
}
}
export function isStep(size: number | Step | 'container' | 'merged'): size is Step {
return isObject(size) && size['step'] !== undefined;
}
// TODO(https://github.com/vega/vega-lite/issues/2503): Make this generic so we can support some form of top-down sizing.
/**
* Common properties for specifying width and height of unit and layer specifications.
*/
export interface LayoutSizeMixins {
/**
* The width of a visualization.
*
* - For a plot with a continuous x-field, width should be a number.
* - For a plot with either a discrete x-field or no x-field, width can be either a number indicating a fixed width or an object in the form of `{step: number}` defining the width per discrete step. (No x-field is equivalent to having one discrete step.)
* - To enable responsive sizing on width, it should be set to `"container"`.
*
* __Default value:__
* Based on `config.view.continuousWidth` for a plot with a continuous x-field and `config.view.discreteWidth` otherwise.
*
* __Note:__ For plots with [`row` and `column` channels](https://vega.github.io/vega-lite/docs/encoding.html#facet), this represents the width of a single view and the `"container"` option cannot be used. However, if the [`facet`](https://vega.github.io/vega-lite/docs/facet.html#facet-operator) operator is used directly this represents the width of all facets combined. As such, `"container"` can be used and the behavior specified by the [`autosize`](https://vega.github.io/vega-lite/docs/size.html#autosize) property is honored.
*
* __See also:__ [`width`](https://vega.github.io/vega-lite/docs/size.html) documentation.
*/
width?: number | 'container' | Step; // Vega also supports SignalRef for width and height. However, we need to know if width is a step or not in VL and it's very difficult to check this at runtime, so we intentionally do not support SignalRef here.
/**
* The height of a visualization.
*
* - For a plot with a continuous y-field, height should be a number.
* - For a plot with either a discrete y-field or no y-field, height can be either a number indicating a fixed height or an object in the form of `{step: number}` defining the height per discrete step. (No y-field is equivalent to having one discrete step.)
* - To enable responsive sizing on height, it should be set to `"container"`.
*
* __Default value:__ Based on `config.view.continuousHeight` for a plot with a continuous y-field and `config.view.discreteHeight` otherwise.
*
* __Note:__ For plots with [`row` and `column` channels](https://vega.github.io/vega-lite/docs/encoding.html#facet), this represents the height of a single view and the `"container"` option cannot be used. However, if the [`facet`](https://vega.github.io/vega-lite/docs/facet.html#facet-operator) operator is used directly this represents the height of all facets combined. As such, `"container"` can be used and honors the behavior specified by the [`autosize`](https://vega.github.io/vega-lite/docs/size.html#autosize) property is honored.
*
* __See also:__ [`height`](https://vega.github.io/vega-lite/docs/size.html) documentation.
*/
height?: number | 'container' | Step; // Vega also supports SignalRef for width and height. However, we need to know if width is a step or not in VL and it's very difficult to check this at runtime, so we intentionally do not support SignalRef here.
}
export interface LayoutSizeNoStepMixins extends LayoutSizeMixins {
/**
* The combined width of all children of this visualization.
*
* - For all plots, width should be a number.
* - To enable responsive sizing on width, it should be set to `"container"`.
*
* __Default value:__ The sum of all child plot widths.
*
* __See also:__ [`width`](https://vega.github.io/vega-lite/docs/size.html) documentation.
*/
width?: number | 'container';
/**
* The combined height of all children of this visualization.
*
* - For all plots, height should be a number.
* - To enable responsive sizing on height, it should be set to `"container"`.
*
* __Default value:__ THe sum of all child plot heights.
*
* __See also:__ [`height`](https://vega.github.io/vega-lite/docs/size.html) documentation.
*/
height?: number | 'container';
}
export type LayoutSizeField = keyof LayoutSizeMixins;
export type LayoutSizeNoStepField = keyof LayoutSizeNoStepMixins;
export function isFrameMixins(o: any): o is FrameMixins<any> {
return o['view'] || o['width'] || o['height'];
}
export interface FrameMixins<ES extends ExprRef | SignalRef = ExprRef | SignalRef> extends LayoutSizeMixins {
/**
* An object defining the view background's fill and stroke.
*
* __Default value:__ none (transparent)
*/
view?: ViewBackground<ES>;
}
export interface ResolveMixins {
/**
* Scale, axis, and legend resolutions for view composition specifications.
*/
resolve?: Resolve;
}
export interface BaseViewBackground<ES extends ExprRef | SignalRef>
extends Partial<
Pick<
MarkConfig<ES>,
| 'cornerRadius'
| 'fillOpacity'
| 'opacity'
| 'strokeCap'
| 'strokeDash'
| 'strokeDashOffset'
| 'strokeJoin'
| 'strokeMiterLimit'
| 'strokeOpacity'
| 'strokeWidth'
>
> {
// Override documentations for fill, stroke, and cursor
/**
* The fill color.
*
* __Default value:__ `undefined`
*/
fill?: Color | null | ES;
/**
* The stroke color.
*
* __Default value:__ `"#ddd"`
*/
stroke?: Color | null | ES;
/**
* The mouse cursor used over the view. Any valid [CSS cursor type](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#Values) can be used.
*/
cursor?: Cursor;
}
export interface ViewBackground<ES extends ExprRef | SignalRef> extends BaseViewBackground<ES> {
/**
* A string or array of strings indicating the name of custom styles to apply to the view background. A style is a named collection of mark property defaults defined within the [style configuration](https://vega.github.io/vega-lite/docs/mark.html#style-config). If style is an array, later styles will override earlier styles.
*
* __Default value:__ `"cell"`
* __Note:__ Any specified view background properties will augment the default style.
*/
style?: string | string[];
}
export interface BoundsMixins {
/**
* The bounds calculation method to use for determining the extent of a sub-plot. One of `full` (the default) or `flush`.
*
* - If set to `full`, the entire calculated bounds (including axes, title, and legend) will be used.
* - If set to `flush`, only the specified width and height values for the sub-view will be used. The `flush` setting can be useful when attempting to place sub-plots without axes or legends into a uniform grid structure.
*
* __Default value:__ `"full"`
*/
bounds?: 'full' | 'flush';
}
/**
* Base layout for FacetSpec and RepeatSpec.
* This is named "GenericComposition" layout as ConcatLayout is a GenericCompositionLayout too
* (but _not_ vice versa).
*/
export interface GenericCompositionLayout extends BoundsMixins {
/**
* The alignment to apply to grid rows and columns.
* The supported string values are `"all"`, `"each"`, and `"none"`.
*
* - For `"none"`, a flow layout will be used, in which adjacent subviews are simply placed one after the other.
* - For `"each"`, subviews will be aligned into a clean grid structure, but each row or column may be of variable size.
* - For `"all"`, subviews will be aligned and each row or column will be sized identically based on the maximum observed size. String values for this property will be applied to both grid rows and columns.
*
* Alternatively, an object value of the form `{"row": string, "column": string}` can be used to supply different alignments for rows and columns.
*
* __Default value:__ `"all"`.
*/
align?: LayoutAlign | RowCol<LayoutAlign>;
/**
* Boolean flag indicating if subviews should be centered relative to their respective rows or columns.
*
* An object value of the form `{"row": boolean, "column": boolean}` can be used to supply different centering values for rows and columns.
*
* __Default value:__ `false`
*/
center?: boolean | RowCol<boolean>;
/**
* The spacing in pixels between sub-views of the composition operator.
* An object of the form `{"row": number, "column": number}` can be used to set
* different spacing values for rows and columns.
*
* __Default value__: Depends on `"spacing"` property of [the view composition configuration](https://vega.github.io/vega-lite/docs/config.html#view-config) (`20` by default)
*/
spacing?: number | RowCol<number>;
}
export const DEFAULT_SPACING = 20;
export interface ColumnMixins {
/**
* The number of columns to include in the view composition layout.
*
* __Default value__: `undefined` -- An infinite number of columns (a single row) will be assumed. This is equivalent to
* `hconcat` (for `concat`) and to using the `column` channel (for `facet` and `repeat`).
*
* __Note__:
*
* 1) This property is only for:
* - the general (wrappable) `concat` operator (not `hconcat`/`vconcat`)
* - the `facet` and `repeat` operator with one field/repetition definition (without row/column nesting)
*
* 2) Setting the `columns` to `1` is equivalent to `vconcat` (for `concat`) and to using the `row` channel (for `facet` and `repeat`).
*/
columns?: number;
}
export type GenericCompositionLayoutWithColumns = GenericCompositionLayout & ColumnMixins;
export type CompositionConfig = ColumnMixins & {
/**
* The default spacing in pixels between composed sub-views.
*
* __Default value__: `20`
*/
spacing?: number;
};
export interface CompositionConfigMixins {
/** Default configuration for the `facet` view composition operator */
facet?: CompositionConfig;
/** Default configuration for all concatenation and repeat view composition operators (`concat`, `hconcat`, `vconcat`, and `repeat`) */
concat?: CompositionConfig;
}
const COMPOSITION_LAYOUT_INDEX: Flag<keyof GenericCompositionLayoutWithColumns> = {
align: 1,
bounds: 1,
center: 1,
columns: 1,
spacing: 1
};
const COMPOSITION_LAYOUT_PROPERTIES = keys(COMPOSITION_LAYOUT_INDEX);
export type SpecType = 'unit' | 'facet' | 'layer' | 'concat';
export function extractCompositionLayout(
spec: NormalizedSpec,
specType: keyof CompositionConfigMixins,
config: CompositionConfigMixins
): GenericCompositionLayoutWithColumns {
const compositionConfig = config[specType];
const layout: GenericCompositionLayoutWithColumns = {};
// Apply config first
const {spacing: spacingConfig, columns} = compositionConfig;
if (spacingConfig !== undefined) {
layout.spacing = spacingConfig;
}
if (columns !== undefined) {
if ((isFacetSpec(spec) && !isFacetMapping(spec.facet)) || isConcatSpec(spec)) {
layout.columns = columns;
}
}
if (isVConcatSpec(spec)) {
layout.columns = 1;
}
// Then copy properties from the spec
for (const prop of COMPOSITION_LAYOUT_PROPERTIES) {
if (spec[prop] !== undefined) {
if (prop === 'spacing') {
const spacing: number | RowCol<number> = spec[prop];
layout[prop] = isNumber(spacing)
? spacing
: {
row: spacing.row ?? spacingConfig,
column: spacing.column ?? spacingConfig
};
} else {
(layout[prop] as any) = spec[prop];
}
}
}
return layout;
}