ggaction 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/package.json +84 -0
- package/src/AGENTS.md +119 -0
- package/src/ChartProgram.js +10 -0
- package/src/actions/boxPlots/components.js +88 -0
- package/src/actions/boxPlots/create.js +84 -0
- package/src/actions/boxPlots/index.js +10 -0
- package/src/actions/boxPlots/materialize.js +183 -0
- package/src/actions/boxPlots/options.js +147 -0
- package/src/actions/boxPlots/resolve.js +38 -0
- package/src/actions/canvas/actions.js +144 -0
- package/src/actions/canvas/index.js +6 -0
- package/src/actions/coordinates/actions.js +85 -0
- package/src/actions/coordinates/index.js +5 -0
- package/src/actions/data/box.js +40 -0
- package/src/actions/data/create.js +33 -0
- package/src/actions/data/density.js +62 -0
- package/src/actions/data/derived.js +52 -0
- package/src/actions/data/filter.js +168 -0
- package/src/actions/data/index.js +32 -0
- package/src/actions/data/interval.js +69 -0
- package/src/actions/data/regression.js +71 -0
- package/src/actions/data/shared.js +25 -0
- package/src/actions/encodings/appearance.js +243 -0
- package/src/actions/encodings/barWidth.js +60 -0
- package/src/actions/encodings/color.js +329 -0
- package/src/actions/encodings/density.js +225 -0
- package/src/actions/encodings/histogram.js +50 -0
- package/src/actions/encodings/index.js +23 -0
- package/src/actions/encodings/offset.js +104 -0
- package/src/actions/encodings/position/policies/area.js +24 -0
- package/src/actions/encodings/position/policies/bar.js +110 -0
- package/src/actions/encodings/position/policies/common.js +17 -0
- package/src/actions/encodings/position/policies/index.js +25 -0
- package/src/actions/encodings/position/policies/line.js +74 -0
- package/src/actions/encodings/position/policies/point.js +19 -0
- package/src/actions/encodings/position/policies/rule.js +17 -0
- package/src/actions/encodings/position/resolve.js +170 -0
- package/src/actions/encodings/position.js +176 -0
- package/src/actions/encodings/ranged.js +276 -0
- package/src/actions/encodings/ruleAppearance.js +47 -0
- package/src/actions/encodings/shared.js +164 -0
- package/src/actions/encodings/strokeDash.js +145 -0
- package/src/actions/errorBands/components.js +81 -0
- package/src/actions/errorBands/create.js +159 -0
- package/src/actions/errorBands/index.js +7 -0
- package/src/actions/errorBands/resolve.js +28 -0
- package/src/actions/errorBars/components.js +62 -0
- package/src/actions/errorBars/create.js +144 -0
- package/src/actions/errorBars/index.js +7 -0
- package/src/actions/errorBars/resolve.js +25 -0
- package/src/actions/guides/axes/axes.js +204 -0
- package/src/actions/guides/axes/axis.js +131 -0
- package/src/actions/guides/axes/index.js +17 -0
- package/src/actions/guides/axes/labels.js +231 -0
- package/src/actions/guides/axes/lines.js +149 -0
- package/src/actions/guides/axes/policy.js +155 -0
- package/src/actions/guides/axes/tickGroups.js +149 -0
- package/src/actions/guides/axes/ticks.js +179 -0
- package/src/actions/guides/axes/titles.js +264 -0
- package/src/actions/guides/grids/grid.js +228 -0
- package/src/actions/guides/grids/index.js +1 -0
- package/src/actions/guides/grids/resolve.js +258 -0
- package/src/actions/guides/guides.js +138 -0
- package/src/actions/guides/index.js +11 -0
- package/src/actions/guides/legends/categorical/actions.js +329 -0
- package/src/actions/guides/legends/categorical/components.js +140 -0
- package/src/actions/guides/legends/categorical/index.js +49 -0
- package/src/actions/guides/legends/categorical/layout.js +445 -0
- package/src/actions/guides/legends/categorical/options.js +188 -0
- package/src/actions/guides/legends/categorical/recipes.js +120 -0
- package/src/actions/guides/legends/categorical/resolve.js +131 -0
- package/src/actions/guides/legends/categorical/symbols.js +280 -0
- package/src/actions/guides/legends/categorical/validation.js +27 -0
- package/src/actions/guides/legends/continuous/common.js +273 -0
- package/src/actions/guides/legends/continuous/gradient.js +363 -0
- package/src/actions/guides/legends/continuous/index.js +25 -0
- package/src/actions/guides/legends/continuous/interval.js +234 -0
- package/src/actions/guides/legends/continuous/opacity.js +382 -0
- package/src/actions/guides/legends/edit.js +314 -0
- package/src/actions/guides/legends/index.js +9 -0
- package/src/actions/guides/legends/size.js +220 -0
- package/src/actions/guides/tickValues.js +84 -0
- package/src/actions/index.js +31 -0
- package/src/actions/intervals/resolve.js +352 -0
- package/src/actions/marks/area.js +369 -0
- package/src/actions/marks/bar/create.js +46 -0
- package/src/actions/marks/bar/edit.js +90 -0
- package/src/actions/marks/bar/index.js +9 -0
- package/src/actions/marks/bar/materialize.js +130 -0
- package/src/actions/marks/index.js +13 -0
- package/src/actions/marks/line.js +248 -0
- package/src/actions/marks/point.js +420 -0
- package/src/actions/marks/rule.js +287 -0
- package/src/actions/marks/shared.js +112 -0
- package/src/actions/primitives/createGraphics.js +163 -0
- package/src/actions/primitives/editGraphics.js +208 -0
- package/src/actions/primitives/index.js +9 -0
- package/src/actions/primitives/semantic.js +168 -0
- package/src/actions/primitives/semanticValue.js +182 -0
- package/src/actions/regression/components.js +216 -0
- package/src/actions/regression/create.js +102 -0
- package/src/actions/regression/index.js +23 -0
- package/src/actions/regression/resolve.js +57 -0
- package/src/actions/scales/consumers.js +283 -0
- package/src/actions/scales/create.js +148 -0
- package/src/actions/scales/definitions.js +319 -0
- package/src/actions/scales/edit.js +290 -0
- package/src/actions/scales/index.js +11 -0
- package/src/actions/scales/materialize.js +466 -0
- package/src/actions/scales/quantitativeColor.js +45 -0
- package/src/actions/selection/actions.js +384 -0
- package/src/actions/selection/index.js +23 -0
- package/src/actions/titles/actions.js +235 -0
- package/src/actions/titles/index.js +19 -0
- package/src/actions/titles/resolve.js +321 -0
- package/src/core/ChartProgram.js +316 -0
- package/src/core/action.js +120 -0
- package/src/core/identifiers.js +28 -0
- package/src/core/immutable.js +94 -0
- package/src/core/specs.js +29 -0
- package/src/core/textMetrics.js +23 -0
- package/src/core/validation.js +54 -0
- package/src/core/vocabulary.js +58 -0
- package/src/extension.js +2 -0
- package/src/grammar/aggregate.js +259 -0
- package/src/grammar/areaSeries.js +217 -0
- package/src/grammar/bars/aggregate.js +98 -0
- package/src/grammar/bars/geometry.js +54 -0
- package/src/grammar/bars/policy.js +90 -0
- package/src/grammar/boxPlot.js +140 -0
- package/src/grammar/coordinates.js +25 -0
- package/src/grammar/curveCommands.js +356 -0
- package/src/grammar/density.js +296 -0
- package/src/grammar/filter.js +169 -0
- package/src/grammar/histogram.js +332 -0
- package/src/grammar/interval.js +343 -0
- package/src/grammar/lineSeries.js +197 -0
- package/src/grammar/markFilter.js +46 -0
- package/src/grammar/markSelection.js +326 -0
- package/src/grammar/palettes.js +208 -0
- package/src/grammar/pathCommands.js +86 -0
- package/src/grammar/pointShapes.js +164 -0
- package/src/grammar/positionCompatibility.js +32 -0
- package/src/grammar/regression.js +578 -0
- package/src/grammar/ruleAppearance.js +12 -0
- package/src/grammar/rules.js +90 -0
- package/src/grammar/scales/appearance.js +185 -0
- package/src/grammar/scales/color.js +349 -0
- package/src/grammar/scales/continuous.js +111 -0
- package/src/grammar/scales/definition.js +153 -0
- package/src/grammar/scales/discretized.js +159 -0
- package/src/grammar/scales/fields.js +133 -0
- package/src/grammar/scales/index.js +12 -0
- package/src/grammar/scales/mapping.js +27 -0
- package/src/grammar/scales/ordinal.js +221 -0
- package/src/grammar/scales/policies.js +50 -0
- package/src/grammar/scales/temporal.js +53 -0
- package/src/grammar/scales/transformed.js +236 -0
- package/src/grammar/scales/types.js +102 -0
- package/src/grammar/scales/validation.js +66 -0
- package/src/grammar/scales.js +1 -0
- package/src/grammar/schemas/concreteGraphic.js +73 -0
- package/src/grammar/schemas/graphic.js +131 -0
- package/src/grammar/schemas/graphicBounds.js +146 -0
- package/src/grammar/schemas/graphicTree.js +133 -0
- package/src/grammar/schemas/mark.js +4 -0
- package/src/grammar/schemas/semanticPath.js +174 -0
- package/src/grammar/seriesLayout.js +76 -0
- package/src/grammar/ticks.js +149 -0
- package/src/grammar/transforms.js +37 -0
- package/src/index.js +2 -0
- package/src/layout/canvas.js +114 -0
- package/src/layout/grid.js +17 -0
- package/src/layout/legend.js +51 -0
- package/src/layout/text.js +62 -0
- package/src/layout/title.js +107 -0
- package/src/materialization/bars/aggregate.js +184 -0
- package/src/materialization/bars/grouped.js +117 -0
- package/src/materialization/bars/histogram.js +163 -0
- package/src/materialization/bars/ranged.js +53 -0
- package/src/materialization/bars/resolve.js +56 -0
- package/src/materialization/dataProvenance.js +15 -0
- package/src/materialization/density.js +29 -0
- package/src/materialization/dependencies.js +134 -0
- package/src/materialization/encodings.js +50 -0
- package/src/materialization/graphicHierarchy.js +104 -0
- package/src/materialization/legends.js +20 -0
- package/src/materialization/marks.js +110 -0
- package/src/materialization/planner.js +38 -0
- package/src/materialization/selection/filter.js +40 -0
- package/src/materialization/selection/items.js +503 -0
- package/src/materialization/selection/path.js +37 -0
- package/src/materialization/selection/point.js +118 -0
- package/src/materialization/selection/policies/area.js +10 -0
- package/src/materialization/selection/policies/bar.js +10 -0
- package/src/materialization/selection/policies/index.js +40 -0
- package/src/materialization/selection/policies/line.js +10 -0
- package/src/materialization/selection/policies/point.js +10 -0
- package/src/materialization/selection/policies/rule.js +10 -0
- package/src/materialization/selection/state.js +67 -0
- package/src/materialization/selection/styles.js +183 -0
- package/src/renderers/canvas/circle.js +62 -0
- package/src/renderers/canvas/index.js +132 -0
- package/src/renderers/canvas/line.js +54 -0
- package/src/renderers/canvas/path.js +98 -0
- package/src/renderers/canvas/rect.js +57 -0
- package/src/renderers/canvas/text.js +96 -0
- package/src/renderers/canvas/validation.js +23 -0
- package/src/renderers/png.js +29 -0
- package/src/selectors/coordinates.js +13 -0
- package/src/selectors/datasets.js +13 -0
- package/src/selectors/index.js +4 -0
- package/src/selectors/layers.js +32 -0
- package/src/selectors/scales.js +21 -0
- package/src/theme/defaults.js +13 -0
- package/types/extension.d.ts +14 -0
- package/types/index.d.ts +81 -0
- package/types/png.d.ts +14 -0
- package/types/program.d.ts +1096 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
2
|
+
import { isPlainObject } from "../../../core/immutable.js";
|
|
3
|
+
import {
|
|
4
|
+
validateKeys,
|
|
5
|
+
validateNonEmptyString,
|
|
6
|
+
validateNonNegativeFinite
|
|
7
|
+
} from "../../../core/validation.js";
|
|
8
|
+
import {
|
|
9
|
+
isTransformedScaleType,
|
|
10
|
+
mapContinuousScaleValues
|
|
11
|
+
} from "../../../grammar/scales.js";
|
|
12
|
+
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
13
|
+
import { resolveGridLineGeometry } from "../../../layout/grid.js";
|
|
14
|
+
import { DEFAULT_COLORS } from "../../../theme/defaults.js";
|
|
15
|
+
import { findCoordinate } from "../../../selectors/coordinates.js";
|
|
16
|
+
import {
|
|
17
|
+
inferGridTickConfig,
|
|
18
|
+
valuesFromTickConfig
|
|
19
|
+
} from "../tickValues.js";
|
|
20
|
+
import {
|
|
21
|
+
findGraphicParent,
|
|
22
|
+
walkGraphicDrawOrder
|
|
23
|
+
} from "../../../grammar/schemas/graphicTree.js";
|
|
24
|
+
|
|
25
|
+
const GRID_OPTIONS = Object.freeze([
|
|
26
|
+
"scale", "coordinate", "count", "values", "color", "lineWidth", "strokeDash"
|
|
27
|
+
]);
|
|
28
|
+
const GRID_EDIT_OPTIONS = Object.freeze([
|
|
29
|
+
"count", "values", "color", "lineWidth", "strokeDash"
|
|
30
|
+
]);
|
|
31
|
+
const DEFAULT_STYLE = Object.freeze({
|
|
32
|
+
color: DEFAULT_COLORS.grid,
|
|
33
|
+
lineWidth: 1,
|
|
34
|
+
strokeDash: Object.freeze([])
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export function gridNames(direction) {
|
|
38
|
+
const prefix = direction === "horizontal" ? "Horizontal" : "Vertical";
|
|
39
|
+
return {
|
|
40
|
+
channel: direction === "horizontal" ? "y" : "x",
|
|
41
|
+
create: `create${prefix}Grid`,
|
|
42
|
+
rematerialize: `rematerialize${prefix}Grid`,
|
|
43
|
+
graphic: `${direction}GridLines`
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function validateStrokeDash(value) {
|
|
48
|
+
if (
|
|
49
|
+
!Array.isArray(value) ||
|
|
50
|
+
value.length % 2 !== 0 ||
|
|
51
|
+
!value.every(item => Number.isFinite(item) && item >= 0)
|
|
52
|
+
) {
|
|
53
|
+
throw new TypeError(
|
|
54
|
+
"Grid strokeDash must be an even-length array of non-negative finite numbers."
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function validateGridCreateArgs(args, operation) {
|
|
61
|
+
if (!isPlainObject(args)) {
|
|
62
|
+
throw new TypeError(`${operation} options must be a plain object.`);
|
|
63
|
+
}
|
|
64
|
+
validateKeys(args, GRID_OPTIONS, operation);
|
|
65
|
+
if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
|
|
66
|
+
throw new Error(`${operation} cannot use count and values together.`);
|
|
67
|
+
}
|
|
68
|
+
if (Object.hasOwn(args, "count") && (!Number.isInteger(args.count) || args.count <= 0)) {
|
|
69
|
+
throw new RangeError("Grid count must be a positive integer.");
|
|
70
|
+
}
|
|
71
|
+
if (Object.hasOwn(args, "values") && (
|
|
72
|
+
!Array.isArray(args.values) ||
|
|
73
|
+
args.values.length === 0 ||
|
|
74
|
+
!args.values.every(Number.isFinite)
|
|
75
|
+
)) {
|
|
76
|
+
throw new TypeError("Grid values must be a non-empty finite number array.");
|
|
77
|
+
}
|
|
78
|
+
if (Object.hasOwn(args, "color")) {
|
|
79
|
+
validateNonEmptyString(args.color, "Grid color");
|
|
80
|
+
}
|
|
81
|
+
if (Object.hasOwn(args, "lineWidth")) {
|
|
82
|
+
validateNonNegativeFinite(args.lineWidth, "Grid lineWidth");
|
|
83
|
+
}
|
|
84
|
+
if (Object.hasOwn(args, "strokeDash")) validateStrokeDash(args.strokeDash);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function validateGridEditArgs(args, operation) {
|
|
88
|
+
if (!isPlainObject(args)) {
|
|
89
|
+
throw new TypeError(`${operation} options must be a plain object.`);
|
|
90
|
+
}
|
|
91
|
+
validateKeys(args, GRID_EDIT_OPTIONS, operation);
|
|
92
|
+
if (Object.keys(args).length === 0) {
|
|
93
|
+
throw new TypeError(`${operation} requires at least one option.`);
|
|
94
|
+
}
|
|
95
|
+
if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
|
|
96
|
+
throw new Error(`${operation} cannot use count and values together.`);
|
|
97
|
+
}
|
|
98
|
+
if (Object.hasOwn(args, "count") && (
|
|
99
|
+
!Number.isInteger(args.count) || args.count <= 0
|
|
100
|
+
)) {
|
|
101
|
+
throw new RangeError("Grid count must be a positive integer.");
|
|
102
|
+
}
|
|
103
|
+
if (Object.hasOwn(args, "values") && args.values !== "auto" && (
|
|
104
|
+
!Array.isArray(args.values) ||
|
|
105
|
+
args.values.length === 0 ||
|
|
106
|
+
!args.values.every(Number.isFinite)
|
|
107
|
+
)) {
|
|
108
|
+
throw new TypeError(
|
|
109
|
+
'Grid values must be "auto" or a non-empty finite number array.'
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
if (Object.hasOwn(args, "color")) {
|
|
113
|
+
validateNonEmptyString(args.color, "Grid color");
|
|
114
|
+
}
|
|
115
|
+
if (Object.hasOwn(args, "lineWidth")) {
|
|
116
|
+
validateNonNegativeFinite(args.lineWidth, "Grid lineWidth");
|
|
117
|
+
}
|
|
118
|
+
if (Object.hasOwn(args, "strokeDash")) validateStrokeDash(args.strokeDash);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function resolveGridResources(program, direction, args) {
|
|
122
|
+
const { channel } = gridNames(direction);
|
|
123
|
+
let layers = program.semanticSpec.layers.filter(
|
|
124
|
+
layer => layer.encoding?.[channel]?.scale !== undefined
|
|
125
|
+
);
|
|
126
|
+
if (layers.length === 0) throw new Error(`${direction} grid requires a ${channel} encoding.`);
|
|
127
|
+
const requestedCoordinate = args.coordinate === undefined
|
|
128
|
+
? undefined
|
|
129
|
+
: validateUserId(args.coordinate, "Grid coordinate id");
|
|
130
|
+
const requestedScale = args.scale === undefined
|
|
131
|
+
? undefined
|
|
132
|
+
: validateUserId(args.scale, "Grid scale id");
|
|
133
|
+
if (requestedCoordinate !== undefined) {
|
|
134
|
+
layers = layers.filter(layer => layer.coordinate === requestedCoordinate);
|
|
135
|
+
}
|
|
136
|
+
if (requestedScale !== undefined) {
|
|
137
|
+
layers = layers.filter(layer => layer.encoding[channel].scale === requestedScale);
|
|
138
|
+
}
|
|
139
|
+
if (layers.length === 0) throw new Error(`${direction} grid found no matching encoded layers.`);
|
|
140
|
+
const coordinateIds = [...new Set(layers.map(layer => layer.coordinate).filter(Boolean))];
|
|
141
|
+
if (layers.some(layer => layer.coordinate === undefined)) {
|
|
142
|
+
throw new Error(`${direction} grid requires a stored coordinate.`);
|
|
143
|
+
}
|
|
144
|
+
if (coordinateIds.length === 0) throw new Error(`Unknown grid coordinate "${requestedCoordinate}".`);
|
|
145
|
+
if (coordinateIds.length > 1) {
|
|
146
|
+
throw new Error(`${direction} grid found multiple coordinates; provide coordinate explicitly.`);
|
|
147
|
+
}
|
|
148
|
+
const coordinate = requestedCoordinate ?? coordinateIds[0];
|
|
149
|
+
const definition = findCoordinate(program, coordinate);
|
|
150
|
+
if (definition?.type !== "cartesian") {
|
|
151
|
+
throw new Error(`${direction} grid requires a Cartesian coordinate.`);
|
|
152
|
+
}
|
|
153
|
+
const scaleIds = [...new Set(layers.map(layer => layer.encoding[channel].scale))];
|
|
154
|
+
if (scaleIds.length === 0) throw new Error(`Unknown grid scale "${requestedScale}".`);
|
|
155
|
+
if (scaleIds.length > 1) {
|
|
156
|
+
throw new Error(`${direction} grid found multiple ${channel} scales; provide scale explicitly.`);
|
|
157
|
+
}
|
|
158
|
+
const scale = requestedScale ?? scaleIds[0];
|
|
159
|
+
if (
|
|
160
|
+
!["linear", "time"].includes(program.resolvedScales[scale]?.type) &&
|
|
161
|
+
!isTransformedScaleType(program.resolvedScales[scale]?.type)
|
|
162
|
+
) {
|
|
163
|
+
throw new Error(`${direction} grid requires resolved continuous scale "${scale}".`);
|
|
164
|
+
}
|
|
165
|
+
const related = new Set(layers.map(layer => layer.id));
|
|
166
|
+
let before;
|
|
167
|
+
walkGraphicDrawOrder(program.graphicSpec, ({ id }) => {
|
|
168
|
+
if (before === undefined && related.has(id)) before = id;
|
|
169
|
+
});
|
|
170
|
+
if (before === undefined) throw new Error(`${direction} grid requires related mark graphics.`);
|
|
171
|
+
const owner = findGraphicParent(program.graphicSpec, before);
|
|
172
|
+
return {
|
|
173
|
+
channel,
|
|
174
|
+
coordinate,
|
|
175
|
+
scale,
|
|
176
|
+
before,
|
|
177
|
+
...(owner?.kind === "parent" ? { parent: owner.id } : {})
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function resolveGridConfig(program, direction, args, resources) {
|
|
182
|
+
const hasExplicitValues = Object.hasOwn(args, "values") ||
|
|
183
|
+
Object.hasOwn(args, "count");
|
|
184
|
+
const explicit = Object.hasOwn(args, "values")
|
|
185
|
+
? { mode: "values", values: args.values }
|
|
186
|
+
: Object.hasOwn(args, "count")
|
|
187
|
+
? { mode: "count", count: args.count }
|
|
188
|
+
: inferGridTickConfig(program, resources.channel, resources.scale);
|
|
189
|
+
return {
|
|
190
|
+
direction,
|
|
191
|
+
scale: resources.scale,
|
|
192
|
+
coordinate: resources.coordinate,
|
|
193
|
+
...explicit,
|
|
194
|
+
inferredValues: !hasExplicitValues,
|
|
195
|
+
color: args.color ?? DEFAULT_STYLE.color,
|
|
196
|
+
lineWidth: args.lineWidth ?? DEFAULT_STYLE.lineWidth,
|
|
197
|
+
strokeDash: args.strokeDash ?? DEFAULT_STYLE.strokeDash
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function refreshGridConfig(program, config) {
|
|
202
|
+
if (config.inferredValues !== true) return config;
|
|
203
|
+
const channel = config.direction === "horizontal" ? "y" : "x";
|
|
204
|
+
const refreshed = {
|
|
205
|
+
...config,
|
|
206
|
+
...inferGridTickConfig(program, channel, config.scale)
|
|
207
|
+
};
|
|
208
|
+
if (refreshed.mode === "values") delete refreshed.count;
|
|
209
|
+
else delete refreshed.values;
|
|
210
|
+
return refreshed;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function editGridConfig(previous, args) {
|
|
214
|
+
const config = {
|
|
215
|
+
...previous,
|
|
216
|
+
...Object.fromEntries(
|
|
217
|
+
Object.entries(args).filter(([key]) => !["count", "values"].includes(key))
|
|
218
|
+
)
|
|
219
|
+
};
|
|
220
|
+
if (Object.hasOwn(args, "count")) {
|
|
221
|
+
config.mode = "count";
|
|
222
|
+
config.count = args.count;
|
|
223
|
+
config.inferredValues = false;
|
|
224
|
+
delete config.values;
|
|
225
|
+
} else if (Array.isArray(args.values)) {
|
|
226
|
+
config.mode = "values";
|
|
227
|
+
config.values = args.values;
|
|
228
|
+
config.inferredValues = false;
|
|
229
|
+
delete config.count;
|
|
230
|
+
} else if (args.values === "auto") {
|
|
231
|
+
config.inferredValues = true;
|
|
232
|
+
}
|
|
233
|
+
return config;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function resolveGridGeometry(program, config) {
|
|
237
|
+
const scale = program.resolvedScales[config.scale];
|
|
238
|
+
const bounds = resolveGraphicBounds(program);
|
|
239
|
+
if ((
|
|
240
|
+
!["linear", "time"].includes(scale?.type) &&
|
|
241
|
+
!isTransformedScaleType(scale?.type)
|
|
242
|
+
) || bounds === undefined) {
|
|
243
|
+
throw new Error("Grid materialization requires a continuous scale and Canvas bounds.");
|
|
244
|
+
}
|
|
245
|
+
const values = valuesFromTickConfig(program, config);
|
|
246
|
+
const low = Math.min(...scale.domain);
|
|
247
|
+
const high = Math.max(...scale.domain);
|
|
248
|
+
if (!values.every(value => value >= low && value <= high)) {
|
|
249
|
+
throw new RangeError("Grid values must be inside the scale domain.");
|
|
250
|
+
}
|
|
251
|
+
const positions = mapContinuousScaleValues(values, scale);
|
|
252
|
+
return resolveGridLineGeometry({
|
|
253
|
+
direction: config.direction,
|
|
254
|
+
values,
|
|
255
|
+
positions,
|
|
256
|
+
bounds
|
|
257
|
+
});
|
|
258
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { isPlainObject } from "../../core/immutable.js";
|
|
3
|
+
import {
|
|
4
|
+
BAR_ORIENTATIONS,
|
|
5
|
+
resolveBarOrientation
|
|
6
|
+
} from "../../grammar/bars/policy.js";
|
|
7
|
+
import { findSemanticScale } from "../../selectors/scales.js";
|
|
8
|
+
|
|
9
|
+
const OPTIONS = Object.freeze(["axes", "grid", "legend"]);
|
|
10
|
+
|
|
11
|
+
function validateGuideOption(value, label) {
|
|
12
|
+
if (value !== undefined && value !== false && !isPlainObject(value)) {
|
|
13
|
+
throw new TypeError(`${label} must be false or a plain object.`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function validateOptions(args) {
|
|
18
|
+
if (!isPlainObject(args)) {
|
|
19
|
+
throw new TypeError("createGuides options must be a plain object.");
|
|
20
|
+
}
|
|
21
|
+
for (const key of Object.keys(args)) {
|
|
22
|
+
if (!OPTIONS.includes(key)) {
|
|
23
|
+
throw new Error(`Unknown createGuides option "${key}".`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
validateGuideOption(args.axes, "createGuides axes");
|
|
27
|
+
validateGuideOption(args.grid, "createGuides grid");
|
|
28
|
+
validateGuideOption(args.legend, "createGuides legend");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hasCartesianEncoding(program) {
|
|
32
|
+
return program.semanticSpec.layers.some(
|
|
33
|
+
layer => layer.encoding?.x !== undefined || layer.encoding?.y !== undefined
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function hasGridEncoding(program) {
|
|
38
|
+
return program.semanticSpec.layers.some(
|
|
39
|
+
layer => layer.encoding?.x?.scale !== undefined ||
|
|
40
|
+
layer.encoding?.y?.scale !== undefined
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function inferGridOptions(program) {
|
|
45
|
+
const barOrientations = program.semanticSpec.layers
|
|
46
|
+
.map(resolveBarOrientation)
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
const positionedLayers = program.semanticSpec.layers.filter(
|
|
49
|
+
layer => layer.encoding?.x !== undefined && layer.encoding?.y !== undefined
|
|
50
|
+
);
|
|
51
|
+
const horizontalQuantitative = positionedLayers.length > 0 &&
|
|
52
|
+
positionedLayers.every(layer =>
|
|
53
|
+
layer.encoding.x.fieldType === "quantitative" &&
|
|
54
|
+
["nominal", "ordinal", "temporal"].includes(layer.encoding.y.fieldType)
|
|
55
|
+
);
|
|
56
|
+
if (
|
|
57
|
+
horizontalQuantitative ||
|
|
58
|
+
(barOrientations.length > 0 &&
|
|
59
|
+
barOrientations.every(value => value === BAR_ORIENTATIONS.horizontal))
|
|
60
|
+
) {
|
|
61
|
+
return { horizontal: false, vertical: true };
|
|
62
|
+
}
|
|
63
|
+
return { horizontal: true, vertical: false };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function inferAxesOptions(program) {
|
|
67
|
+
const horizontalInterval = program.semanticSpec.layers.some(layer =>
|
|
68
|
+
layer.mark?.type === "rule" &&
|
|
69
|
+
layer.encoding?.x?.fieldType === "quantitative" &&
|
|
70
|
+
layer.encoding?.x2?.fieldType === "quantitative" &&
|
|
71
|
+
["nominal", "ordinal", "temporal"].includes(layer.encoding?.y?.fieldType)
|
|
72
|
+
);
|
|
73
|
+
return horizontalInterval
|
|
74
|
+
? { x: { ticksAndLabels: { count: 7 } } }
|
|
75
|
+
: {};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function hasLegendEncoding(program) {
|
|
79
|
+
return program.semanticSpec.layers.some(
|
|
80
|
+
layer =>
|
|
81
|
+
(layer.mark?.type === "point" &&
|
|
82
|
+
layer.encoding?.opacity?.scale !== undefined) ||
|
|
83
|
+
(layer.mark?.type === "point" &&
|
|
84
|
+
layer.encoding?.color?.scale !== undefined &&
|
|
85
|
+
(layer.encoding?.shape?.scale !== undefined ||
|
|
86
|
+
["sequential", "quantize", "quantile", "threshold"].includes(
|
|
87
|
+
findSemanticScale(program, layer.encoding.color.scale)?.type
|
|
88
|
+
))) ||
|
|
89
|
+
(layer.mark?.type === "line" &&
|
|
90
|
+
["color", "strokeDash"].some(
|
|
91
|
+
channel => layer.encoding?.[channel]?.scale !== undefined
|
|
92
|
+
)) ||
|
|
93
|
+
(layer.mark?.type === "bar" &&
|
|
94
|
+
layer.encoding?.color?.scale !== undefined) ||
|
|
95
|
+
(layer.mark?.type === "area" &&
|
|
96
|
+
layer.encoding?.color?.scale !== undefined)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function selectOption(explicit, applicable) {
|
|
101
|
+
if (explicit === false) return undefined;
|
|
102
|
+
if (explicit !== undefined) return explicit;
|
|
103
|
+
return applicable ? {} : undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const createGuides = action(
|
|
107
|
+
{
|
|
108
|
+
op: "createGuides",
|
|
109
|
+
description: "Create applicable axes, grid, and legend."
|
|
110
|
+
},
|
|
111
|
+
function (args = {}) {
|
|
112
|
+
validateOptions(args);
|
|
113
|
+
const axes = args.axes === undefined && hasCartesianEncoding(this)
|
|
114
|
+
? inferAxesOptions(this)
|
|
115
|
+
: selectOption(args.axes, hasCartesianEncoding(this));
|
|
116
|
+
const grid = args.grid === undefined && hasGridEncoding(this)
|
|
117
|
+
? inferGridOptions(this)
|
|
118
|
+
: selectOption(args.grid, hasGridEncoding(this));
|
|
119
|
+
const legend = selectOption(
|
|
120
|
+
args.legend,
|
|
121
|
+
hasLegendEncoding(this)
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (axes === undefined && grid === undefined && legend === undefined) {
|
|
125
|
+
throw new Error("createGuides requires at least one selected guide.");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let next = this;
|
|
129
|
+
if (axes !== undefined) next = next.createAxes(axes);
|
|
130
|
+
if (grid !== undefined) next = next.createGrid(grid);
|
|
131
|
+
if (legend !== undefined) next = next.createLegend(legend);
|
|
132
|
+
return next;
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
export function registerGuideCollectionActions(ProgramClass) {
|
|
137
|
+
ProgramClass.prototype.createGuides = createGuides;
|
|
138
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { registerGuideAxisActions } from "./axes/index.js";
|
|
2
|
+
import { registerGuideCollectionActions } from "./guides.js";
|
|
3
|
+
import { registerGridActions } from "./grids/index.js";
|
|
4
|
+
import { registerLegendActions } from "./legends/index.js";
|
|
5
|
+
|
|
6
|
+
export function registerGuideActions(ProgramClass) {
|
|
7
|
+
registerGuideAxisActions(ProgramClass);
|
|
8
|
+
registerLegendActions(ProgramClass);
|
|
9
|
+
registerGridActions(ProgramClass);
|
|
10
|
+
registerGuideCollectionActions(ProgramClass);
|
|
11
|
+
}
|