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,243 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import {
|
|
3
|
+
readNominalField,
|
|
4
|
+
readQuantitativeField,
|
|
5
|
+
readScaleField,
|
|
6
|
+
validateOpacityValue
|
|
7
|
+
} from "../../grammar/scales.js";
|
|
8
|
+
import {
|
|
9
|
+
resolveAppearanceScaleDefinition,
|
|
10
|
+
resolveOpacityScaleDefinition
|
|
11
|
+
} from "../scales/definitions.js";
|
|
12
|
+
import { findLayer } from "../../selectors/layers.js";
|
|
13
|
+
import { applyMaterializationPlan } from "../../materialization/dependencies.js";
|
|
14
|
+
import { planEncodingRematerialization } from "../../materialization/encodings.js";
|
|
15
|
+
import {
|
|
16
|
+
applyEncodingScale,
|
|
17
|
+
resolveReassignmentScaleOptions,
|
|
18
|
+
resolveTarget,
|
|
19
|
+
validateOptions
|
|
20
|
+
} from "./shared.js";
|
|
21
|
+
|
|
22
|
+
const RADIUS_OPTIONS = Object.freeze(["value", "target"]);
|
|
23
|
+
const OPACITY_OPTIONS = Object.freeze([
|
|
24
|
+
"value", "field", "target", "fieldType", "scale"
|
|
25
|
+
]);
|
|
26
|
+
const FIELD_OPTIONS = Object.freeze(["field", "target", "fieldType", "scale"]);
|
|
27
|
+
function encodeAppearanceField(program, channel, args, operation) {
|
|
28
|
+
validateOptions(args, FIELD_OPTIONS, operation);
|
|
29
|
+
const { id: target, dataset, layer } = resolveTarget(
|
|
30
|
+
program,
|
|
31
|
+
args.target,
|
|
32
|
+
["point"],
|
|
33
|
+
"point mark"
|
|
34
|
+
);
|
|
35
|
+
const expectedFieldType = channel === "shape" ? "nominal" : "quantitative";
|
|
36
|
+
const fieldType = args.fieldType ?? expectedFieldType;
|
|
37
|
+
if (fieldType !== expectedFieldType) {
|
|
38
|
+
throw new Error(`${operation} requires a ${expectedFieldType} field.`);
|
|
39
|
+
}
|
|
40
|
+
if (channel === "size" && program.markConfigs[target]?.radius !== undefined) {
|
|
41
|
+
throw new Error("encodeSize cannot be combined with a constant radius.");
|
|
42
|
+
}
|
|
43
|
+
const previous = layer.encoding?.[channel];
|
|
44
|
+
const requestedScale = resolveReassignmentScaleOptions(
|
|
45
|
+
previous,
|
|
46
|
+
args.scale ?? {}
|
|
47
|
+
);
|
|
48
|
+
const scale = resolveAppearanceScaleDefinition(
|
|
49
|
+
program,
|
|
50
|
+
channel,
|
|
51
|
+
requestedScale
|
|
52
|
+
);
|
|
53
|
+
if (Object.hasOwn(scale, "unknown")) {
|
|
54
|
+
readScaleField(dataset.values, args.field, fieldType, {
|
|
55
|
+
allowUnknown: true
|
|
56
|
+
});
|
|
57
|
+
} else if (channel === "shape") {
|
|
58
|
+
readNominalField(dataset.values, args.field);
|
|
59
|
+
} else {
|
|
60
|
+
readQuantitativeField(dataset.values, args.field);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let next = program
|
|
64
|
+
.editSemantic({
|
|
65
|
+
property: `layer[${target}].encoding.${channel}.field`,
|
|
66
|
+
value: args.field
|
|
67
|
+
})
|
|
68
|
+
.editSemantic({
|
|
69
|
+
property: `layer[${target}].encoding.${channel}.fieldType`,
|
|
70
|
+
value: fieldType
|
|
71
|
+
})
|
|
72
|
+
.editSemantic({
|
|
73
|
+
property: `layer[${target}].encoding.${channel}.scale`,
|
|
74
|
+
value: scale.id
|
|
75
|
+
});
|
|
76
|
+
next = applyEncodingScale(next, scale, requestedScale, {
|
|
77
|
+
reassignment: previous?.scale === scale.id
|
|
78
|
+
});
|
|
79
|
+
return applyMaterializationPlan(
|
|
80
|
+
next,
|
|
81
|
+
planEncodingRematerialization(next, {
|
|
82
|
+
target,
|
|
83
|
+
channel,
|
|
84
|
+
scale: scale.id
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const encodeRadius = action(
|
|
90
|
+
{
|
|
91
|
+
op: "encodeRadius",
|
|
92
|
+
description: "Set a constant graphical radius on a point mark."
|
|
93
|
+
},
|
|
94
|
+
function (args = {}) {
|
|
95
|
+
validateOptions(args, RADIUS_OPTIONS, "encodeRadius");
|
|
96
|
+
const { id: target } = resolveTarget(
|
|
97
|
+
this,
|
|
98
|
+
args.target,
|
|
99
|
+
["point"],
|
|
100
|
+
"point mark"
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (!Number.isFinite(args.value) || args.value < 0) {
|
|
104
|
+
throw new RangeError(
|
|
105
|
+
"encodeRadius requires a non-negative finite value."
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const layer = findLayer(this, target);
|
|
110
|
+
if (layer.encoding?.size !== undefined) {
|
|
111
|
+
throw new Error("encodeRadius cannot be combined with a size encoding.");
|
|
112
|
+
}
|
|
113
|
+
return this
|
|
114
|
+
._withMarkConfig(target, {
|
|
115
|
+
...this.markConfigs[target],
|
|
116
|
+
radius: args.value
|
|
117
|
+
})
|
|
118
|
+
.rematerializePointMark({ id: target });
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const encodeSize = action(
|
|
123
|
+
{
|
|
124
|
+
op: "encodeSize",
|
|
125
|
+
description: "Encode a quantitative field as equal-area point size."
|
|
126
|
+
},
|
|
127
|
+
function (args = {}) {
|
|
128
|
+
return encodeAppearanceField(this, "size", args, "encodeSize");
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const encodeShape = action(
|
|
133
|
+
{
|
|
134
|
+
op: "encodeShape",
|
|
135
|
+
description: "Encode a nominal field as point shape."
|
|
136
|
+
},
|
|
137
|
+
function (args = {}) {
|
|
138
|
+
return encodeAppearanceField(this, "shape", args, "encodeShape");
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const clearOpacityEncoding = action(
|
|
143
|
+
{
|
|
144
|
+
op: "clearOpacityEncoding",
|
|
145
|
+
description: "Remove the semantic field-driven opacity assignment."
|
|
146
|
+
},
|
|
147
|
+
function ({ target } = {}) {
|
|
148
|
+
const layer = findLayer(this, target);
|
|
149
|
+
if (layer?.encoding?.opacity === undefined) return this;
|
|
150
|
+
return this.editSemantic({
|
|
151
|
+
property: `layer[${target}].encoding.opacity`,
|
|
152
|
+
remove: true
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const encodeOpacity = action(
|
|
158
|
+
{
|
|
159
|
+
op: "encodeOpacity",
|
|
160
|
+
description: "Assign constant or field-driven mark opacity."
|
|
161
|
+
},
|
|
162
|
+
function (args = {}) {
|
|
163
|
+
validateOptions(args, OPACITY_OPTIONS, "encodeOpacity");
|
|
164
|
+
const hasValue = Object.hasOwn(args, "value");
|
|
165
|
+
const hasField = Object.hasOwn(args, "field");
|
|
166
|
+
if (hasValue === hasField) {
|
|
167
|
+
throw new Error("encodeOpacity requires exactly one of value or field.");
|
|
168
|
+
}
|
|
169
|
+
const { id: target, dataset, layer } = resolveTarget(
|
|
170
|
+
this,
|
|
171
|
+
args.target,
|
|
172
|
+
["point", "rule"],
|
|
173
|
+
"point or rule mark"
|
|
174
|
+
);
|
|
175
|
+
if (hasValue) {
|
|
176
|
+
validateOpacityValue(args.value, "encodeOpacity");
|
|
177
|
+
const { opacity, ...config } = this.markConfigs[target] ?? {};
|
|
178
|
+
void opacity;
|
|
179
|
+
const withoutLegend = this.guideConfigs.legend?.opacity === undefined
|
|
180
|
+
? this
|
|
181
|
+
: this.removeOpacityLegend();
|
|
182
|
+
const next = withoutLegend
|
|
183
|
+
.clearOpacityEncoding({ target })
|
|
184
|
+
._withoutMaterializationConfig(["marks", target, "opacity"])
|
|
185
|
+
._withMarkConfig(target, { ...config, opacity: args.value });
|
|
186
|
+
return layer.mark.type === "rule"
|
|
187
|
+
? next.rematerializeRuleMark({ id: target })
|
|
188
|
+
: next.rematerializePointMark({ id: target });
|
|
189
|
+
}
|
|
190
|
+
const fieldType = args.fieldType ?? "quantitative";
|
|
191
|
+
if (fieldType !== "quantitative") {
|
|
192
|
+
throw new Error("encodeOpacity requires a quantitative field.");
|
|
193
|
+
}
|
|
194
|
+
const previous = layer.encoding?.opacity;
|
|
195
|
+
const requestedScale = resolveReassignmentScaleOptions(
|
|
196
|
+
previous,
|
|
197
|
+
args.scale ?? {}
|
|
198
|
+
);
|
|
199
|
+
const scale = resolveOpacityScaleDefinition(this, requestedScale);
|
|
200
|
+
if (Object.hasOwn(scale, "unknown")) {
|
|
201
|
+
readScaleField(dataset.values, args.field, fieldType, {
|
|
202
|
+
allowUnknown: true
|
|
203
|
+
});
|
|
204
|
+
} else {
|
|
205
|
+
readQuantitativeField(dataset.values, args.field);
|
|
206
|
+
}
|
|
207
|
+
const { opacity, ...config } = this.markConfigs[target] ?? {};
|
|
208
|
+
void opacity;
|
|
209
|
+
let next = this
|
|
210
|
+
._withoutMaterializationConfig(["marks", target, "opacity"])
|
|
211
|
+
.editSemantic({
|
|
212
|
+
property: `layer[${target}].encoding.opacity.field`,
|
|
213
|
+
value: args.field
|
|
214
|
+
})
|
|
215
|
+
.editSemantic({
|
|
216
|
+
property: `layer[${target}].encoding.opacity.fieldType`,
|
|
217
|
+
value: fieldType
|
|
218
|
+
})
|
|
219
|
+
.editSemantic({
|
|
220
|
+
property: `layer[${target}].encoding.opacity.scale`,
|
|
221
|
+
value: scale.id
|
|
222
|
+
})
|
|
223
|
+
next = applyEncodingScale(next, scale, requestedScale, {
|
|
224
|
+
reassignment: previous?.scale === scale.id
|
|
225
|
+
});
|
|
226
|
+
return applyMaterializationPlan(
|
|
227
|
+
next,
|
|
228
|
+
planEncodingRematerialization(next, {
|
|
229
|
+
target,
|
|
230
|
+
channel: "opacity",
|
|
231
|
+
scale: scale.id
|
|
232
|
+
})
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
export function registerAppearanceEncodingAction(ProgramClass) {
|
|
238
|
+
ProgramClass.prototype.encodeRadius = encodeRadius;
|
|
239
|
+
ProgramClass.prototype.encodeSize = encodeSize;
|
|
240
|
+
ProgramClass.prototype.encodeShape = encodeShape;
|
|
241
|
+
ProgramClass.prototype.encodeOpacity = encodeOpacity;
|
|
242
|
+
ProgramClass.prototype.clearOpacityEncoding = clearOpacityEncoding;
|
|
243
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { resolveTarget, validateOptions } from "./shared.js";
|
|
3
|
+
import {
|
|
4
|
+
BAR_GRAINS,
|
|
5
|
+
resolveBarColorLayout,
|
|
6
|
+
resolveBarGrain
|
|
7
|
+
} from "../../grammar/bars/policy.js";
|
|
8
|
+
import { normalizeBarWidth } from "../../grammar/bars/geometry.js";
|
|
9
|
+
|
|
10
|
+
const OPTIONS = Object.freeze(["band", "pixels", "target"]);
|
|
11
|
+
|
|
12
|
+
const encodeBarWidth = action(
|
|
13
|
+
{
|
|
14
|
+
op: "encodeBarWidth",
|
|
15
|
+
description: "Set aggregate bar width within each x band or xOffset slot."
|
|
16
|
+
},
|
|
17
|
+
function (args = {}) {
|
|
18
|
+
validateOptions(args, OPTIONS, "encodeBarWidth");
|
|
19
|
+
const { id: target, layer } = resolveTarget(
|
|
20
|
+
this,
|
|
21
|
+
args.target,
|
|
22
|
+
["bar"],
|
|
23
|
+
"bar mark"
|
|
24
|
+
);
|
|
25
|
+
const layout = resolveBarColorLayout(layer);
|
|
26
|
+
const width = normalizeBarWidth(
|
|
27
|
+
args,
|
|
28
|
+
this.markConfigs[target]?.barWidth
|
|
29
|
+
);
|
|
30
|
+
const grouped = layout === "group";
|
|
31
|
+
const grain = resolveBarGrain(layer);
|
|
32
|
+
if (grain === BAR_GRAINS.ranged) {
|
|
33
|
+
return this._withMarkConfig(target, { ...this.markConfigs[target], barWidth: width })
|
|
34
|
+
.rematerializeBarMark({ id: target });
|
|
35
|
+
}
|
|
36
|
+
if (
|
|
37
|
+
grain !== BAR_GRAINS.aggregate ||
|
|
38
|
+
(grouped &&
|
|
39
|
+
layer.encoding?.color?.field !== undefined &&
|
|
40
|
+
layer.encoding?.xOffset?.field !== layer.encoding.color.field)
|
|
41
|
+
) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
grouped
|
|
44
|
+
? "encodeBarWidth requires complete grouped bar x, y, color, and xOffset encodings."
|
|
45
|
+
: "encodeBarWidth requires complete aggregate bar x and y encodings."
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return this
|
|
50
|
+
._withMarkConfig(target, {
|
|
51
|
+
...this.markConfigs[target],
|
|
52
|
+
barWidth: width
|
|
53
|
+
})
|
|
54
|
+
.rematerializeBarMark({ id: target });
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
export function registerBarWidthEncodingAction(ProgramClass) {
|
|
59
|
+
ProgramClass.prototype.encodeBarWidth = encodeBarWidth;
|
|
60
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import {
|
|
3
|
+
readNominalField,
|
|
4
|
+
readQuantitativeField,
|
|
5
|
+
readScaleField,
|
|
6
|
+
readTemporalField,
|
|
7
|
+
validateNominalFieldType
|
|
8
|
+
} from "../../grammar/scales.js";
|
|
9
|
+
import {
|
|
10
|
+
BAR_GRAINS,
|
|
11
|
+
inferBarColorLayout,
|
|
12
|
+
resolveBarChannels,
|
|
13
|
+
resolveBarGrain
|
|
14
|
+
} from "../../grammar/bars/policy.js";
|
|
15
|
+
import { validateColorLayout } from "../../grammar/seriesLayout.js";
|
|
16
|
+
import {
|
|
17
|
+
validateAggregate,
|
|
18
|
+
validateAggregateFieldType,
|
|
19
|
+
validateAggregateFieldValues
|
|
20
|
+
} from "../../grammar/aggregate.js";
|
|
21
|
+
import {
|
|
22
|
+
resolveColorScaleDefinition,
|
|
23
|
+
resolveQuantitativeColorScaleDefinition
|
|
24
|
+
} from "../scales/definitions.js";
|
|
25
|
+
import {
|
|
26
|
+
applyEncodingScale,
|
|
27
|
+
resolveReassignmentScaleOptions,
|
|
28
|
+
resolveTarget,
|
|
29
|
+
validateLineSeriesCompatibility,
|
|
30
|
+
validateOptions
|
|
31
|
+
} from "./shared.js";
|
|
32
|
+
import { applyMaterializationPlan } from "../../materialization/dependencies.js";
|
|
33
|
+
import { planEncodingRematerialization } from "../../materialization/encodings.js";
|
|
34
|
+
|
|
35
|
+
const COLOR_ENCODING_OPTIONS = Object.freeze([
|
|
36
|
+
"field", "target", "fieldType", "scale", "layout", "aggregate"
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
function resolveColorLayout(layer, requested, barGrain) {
|
|
40
|
+
const existing = layer.encoding?.color === undefined
|
|
41
|
+
? undefined
|
|
42
|
+
: layer.encoding.color.layout ?? (
|
|
43
|
+
layer.mark.type === "bar"
|
|
44
|
+
? inferBarColorLayout(layer)
|
|
45
|
+
: layer.mark.type === "area"
|
|
46
|
+
? "overlay"
|
|
47
|
+
: undefined
|
|
48
|
+
);
|
|
49
|
+
if (requested !== undefined) validateColorLayout(requested);
|
|
50
|
+
if (existing !== undefined && requested !== undefined && requested !== existing) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Color layout transition from "${existing}" to "${requested}" is not supported.`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const layout = requested ?? existing ?? (
|
|
56
|
+
layer.mark.type === "bar"
|
|
57
|
+
? barGrain === BAR_GRAINS.histogram ? "stack" : barGrain === BAR_GRAINS.ranged ? "overlay" : "group"
|
|
58
|
+
: layer.mark.type === "area"
|
|
59
|
+
? "overlay"
|
|
60
|
+
: undefined
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (["point", "line"].includes(layer.mark.type) && layout !== undefined) {
|
|
64
|
+
throw new Error(`Color layout is not supported for ${layer.mark.type} marks.`);
|
|
65
|
+
}
|
|
66
|
+
if (layer.mark.type === "area" && layout === "group") {
|
|
67
|
+
throw new Error('Area color layout does not support "group".');
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
layer.mark.type === "area" &&
|
|
71
|
+
layer.encoding?.y2 !== undefined &&
|
|
72
|
+
layout !== "overlay"
|
|
73
|
+
) {
|
|
74
|
+
throw new Error('Ranged area color encoding supports only "overlay" layout.');
|
|
75
|
+
}
|
|
76
|
+
return layout;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function applyColorLayoutCompanion(
|
|
80
|
+
program,
|
|
81
|
+
{ target, layer, layout, scale, field }
|
|
82
|
+
) {
|
|
83
|
+
if (layout === undefined) return program;
|
|
84
|
+
if (layer.mark.type === "bar" && resolveBarGrain(layer) === BAR_GRAINS.ranged) return program;
|
|
85
|
+
if (layer.mark.type === "area" && layer.encoding?.y2 !== undefined) {
|
|
86
|
+
return program;
|
|
87
|
+
}
|
|
88
|
+
const channels = layer.mark.type === "bar" ? resolveBarChannels(layer) : undefined;
|
|
89
|
+
const measureChannel = channels?.measure ?? "y";
|
|
90
|
+
const measure = layer.encoding?.[measureChannel];
|
|
91
|
+
if (measure?.field === undefined || measure.scale === undefined) {
|
|
92
|
+
throw new Error(`Color layout on mark "${target}" requires a measure encoding.`);
|
|
93
|
+
}
|
|
94
|
+
const stack = layout === "fill"
|
|
95
|
+
? "normalize"
|
|
96
|
+
: layout === "overlay" || layout === "group"
|
|
97
|
+
? null
|
|
98
|
+
: "zero";
|
|
99
|
+
let next = program;
|
|
100
|
+
if (layout === "group") {
|
|
101
|
+
if (channels?.orientation === "horizontal") {
|
|
102
|
+
throw new Error('Horizontal bars do not support color layout "group" until yOffset is available.');
|
|
103
|
+
}
|
|
104
|
+
next = next.encodeXOffset({
|
|
105
|
+
field,
|
|
106
|
+
target,
|
|
107
|
+
scale: {
|
|
108
|
+
...(layer.encoding?.xOffset?.scale === undefined
|
|
109
|
+
? {}
|
|
110
|
+
: { id: layer.encoding.xOffset.scale }),
|
|
111
|
+
domain: scale.domain
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return next[measureChannel === "x" ? "encodeX" : "encodeY"]({
|
|
116
|
+
target,
|
|
117
|
+
field: measure.field,
|
|
118
|
+
fieldType: measure.fieldType,
|
|
119
|
+
...(measure.aggregate === undefined ? {} : { aggregate: measure.aggregate }),
|
|
120
|
+
stack
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function encodeContinuousColor(program, args) {
|
|
125
|
+
if (!["quantitative", "temporal"].includes(args.fieldType)) {
|
|
126
|
+
throw new Error(`Unsupported color field type "${args.fieldType}".`);
|
|
127
|
+
}
|
|
128
|
+
if (args.layout !== undefined) {
|
|
129
|
+
throw new Error("Continuous color does not support layout.");
|
|
130
|
+
}
|
|
131
|
+
const { id: target, dataset, layer } = resolveTarget(
|
|
132
|
+
program,
|
|
133
|
+
args.target,
|
|
134
|
+
["point", "bar"],
|
|
135
|
+
"continuous color mark"
|
|
136
|
+
);
|
|
137
|
+
const requestedScale = resolveReassignmentScaleOptions(
|
|
138
|
+
layer.encoding?.color,
|
|
139
|
+
args.scale ?? {}
|
|
140
|
+
);
|
|
141
|
+
const scale = resolveQuantitativeColorScaleDefinition(
|
|
142
|
+
program,
|
|
143
|
+
args.fieldType,
|
|
144
|
+
requestedScale
|
|
145
|
+
);
|
|
146
|
+
if (Object.hasOwn(scale, "unknown") && layer.mark.type !== "point") {
|
|
147
|
+
throw new Error(
|
|
148
|
+
"Continuous color scale unknown currently requires a row-owned point mark."
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (
|
|
152
|
+
layer.mark.type === "bar" &&
|
|
153
|
+
layer.encoding?.color?.fieldType === "nominal"
|
|
154
|
+
) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
"Continuous bar color cannot replace an existing nominal color layout."
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
let aggregate;
|
|
160
|
+
if (layer.mark.type === "point") {
|
|
161
|
+
if (args.aggregate !== undefined) {
|
|
162
|
+
throw new Error("Point continuous color does not support aggregate.");
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
if (args.fieldType !== "quantitative") {
|
|
166
|
+
throw new Error("Aggregate bar color currently requires a quantitative field.");
|
|
167
|
+
}
|
|
168
|
+
if (resolveBarGrain(layer) !== BAR_GRAINS.aggregate) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
"Continuous bar color requires a complete categorical aggregate bar."
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const channels = resolveBarChannels(layer);
|
|
174
|
+
const measure = layer.encoding?.[channels.measure];
|
|
175
|
+
aggregate = args.aggregate ?? (
|
|
176
|
+
measure?.field === args.field ? measure.aggregate : undefined
|
|
177
|
+
);
|
|
178
|
+
if (aggregate === undefined) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
"Continuous bar color requires aggregate when its field differs from the measure field."
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
aggregate = validateAggregate(aggregate);
|
|
184
|
+
validateAggregateFieldType(aggregate, args.fieldType);
|
|
185
|
+
validateAggregateFieldValues(dataset.values, args.field, args.fieldType);
|
|
186
|
+
}
|
|
187
|
+
if (Object.hasOwn(scale, "unknown")) {
|
|
188
|
+
readScaleField(dataset.values, args.field, args.fieldType, {
|
|
189
|
+
allowUnknown: true
|
|
190
|
+
});
|
|
191
|
+
} else if (args.fieldType === "temporal") {
|
|
192
|
+
readTemporalField(dataset.values, args.field);
|
|
193
|
+
} else {
|
|
194
|
+
readQuantitativeField(dataset.values, args.field);
|
|
195
|
+
}
|
|
196
|
+
const next = program
|
|
197
|
+
.editSemantic({
|
|
198
|
+
property: `layer[${target}].encoding.color.field`,
|
|
199
|
+
value: args.field
|
|
200
|
+
})
|
|
201
|
+
.editSemantic({
|
|
202
|
+
property: `layer[${target}].encoding.color.fieldType`,
|
|
203
|
+
value: args.fieldType
|
|
204
|
+
})
|
|
205
|
+
.editSemantic({
|
|
206
|
+
property: `layer[${target}].encoding.color.scale`,
|
|
207
|
+
value: scale.id
|
|
208
|
+
});
|
|
209
|
+
const encoded = aggregate === undefined
|
|
210
|
+
? next
|
|
211
|
+
: next.editSemantic({
|
|
212
|
+
property: `layer[${target}].encoding.color.aggregate`,
|
|
213
|
+
value: aggregate
|
|
214
|
+
});
|
|
215
|
+
const scaled = encoded.setQuantitativeColorScale(scale);
|
|
216
|
+
return applyMaterializationPlan(
|
|
217
|
+
scaled,
|
|
218
|
+
planEncodingRematerialization(scaled, {
|
|
219
|
+
target,
|
|
220
|
+
channel: "color",
|
|
221
|
+
scale: scale.id
|
|
222
|
+
})
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const encodeColor = action(
|
|
227
|
+
{
|
|
228
|
+
op: "encodeColor",
|
|
229
|
+
description: "Encode a field as graphical color."
|
|
230
|
+
},
|
|
231
|
+
function (args = {}) {
|
|
232
|
+
validateOptions(args, COLOR_ENCODING_OPTIONS, "encodeColor");
|
|
233
|
+
const requestedFieldType = args.fieldType ?? "nominal";
|
|
234
|
+
if (requestedFieldType !== "nominal") {
|
|
235
|
+
return encodeContinuousColor(this, {
|
|
236
|
+
...args,
|
|
237
|
+
fieldType: requestedFieldType
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (args.aggregate !== undefined) {
|
|
241
|
+
throw new Error("Nominal color does not support aggregate.");
|
|
242
|
+
}
|
|
243
|
+
const fieldType = validateNominalFieldType(requestedFieldType);
|
|
244
|
+
const { id: target, dataset, layer } = resolveTarget(
|
|
245
|
+
this,
|
|
246
|
+
args.target,
|
|
247
|
+
["point", "line", "bar", "area"],
|
|
248
|
+
"color mark"
|
|
249
|
+
);
|
|
250
|
+
if (
|
|
251
|
+
layer.mark.type === "area" &&
|
|
252
|
+
(layer.encoding?.group?.field === undefined ||
|
|
253
|
+
layer.encoding.group.field !== args.field)
|
|
254
|
+
) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
"Area color encoding must match an existing group encoding."
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
validateLineSeriesCompatibility(layer, "color", args.field);
|
|
260
|
+
|
|
261
|
+
const barGrain = resolveBarGrain(layer);
|
|
262
|
+
if (layer.mark.type === "bar" && barGrain === undefined) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
"Bar color encoding requires a complete histogram encoding or a complete ordinal aggregate encoding."
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
const layout = resolveColorLayout(layer, args.layout, barGrain);
|
|
268
|
+
const requestedScale = resolveReassignmentScaleOptions(
|
|
269
|
+
layer.encoding?.color,
|
|
270
|
+
args.scale ?? {}
|
|
271
|
+
);
|
|
272
|
+
const scale = resolveColorScaleDefinition(this, requestedScale);
|
|
273
|
+
if (Object.hasOwn(scale, "unknown") && layer.mark.type !== "point") {
|
|
274
|
+
throw new Error(
|
|
275
|
+
"Nominal color scale unknown currently requires a row-owned point mark."
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
if (Object.hasOwn(scale, "unknown")) {
|
|
279
|
+
readScaleField(dataset.values, args.field, fieldType, {
|
|
280
|
+
allowUnknown: true
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
readNominalField(dataset.values, args.field);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let next = this
|
|
287
|
+
.editSemantic({
|
|
288
|
+
property: `layer[${target}].encoding.color.field`,
|
|
289
|
+
value: args.field
|
|
290
|
+
})
|
|
291
|
+
.editSemantic({
|
|
292
|
+
property: `layer[${target}].encoding.color.fieldType`,
|
|
293
|
+
value: fieldType
|
|
294
|
+
})
|
|
295
|
+
.editSemantic({
|
|
296
|
+
property: `layer[${target}].encoding.color.scale`,
|
|
297
|
+
value: scale.id
|
|
298
|
+
});
|
|
299
|
+
if (layout !== undefined) {
|
|
300
|
+
next = next.editSemantic({
|
|
301
|
+
property: `layer[${target}].encoding.color.layout`,
|
|
302
|
+
value: layout
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
next = applyEncodingScale(next, scale, requestedScale, {
|
|
306
|
+
reassignment: layer.encoding?.color?.scale === scale.id
|
|
307
|
+
});
|
|
308
|
+
next = applyColorLayoutCompanion(next, {
|
|
309
|
+
target,
|
|
310
|
+
layer,
|
|
311
|
+
layout,
|
|
312
|
+
scale,
|
|
313
|
+
field: args.field
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return applyMaterializationPlan(
|
|
317
|
+
next,
|
|
318
|
+
planEncodingRematerialization(next, {
|
|
319
|
+
target,
|
|
320
|
+
channel: "color",
|
|
321
|
+
scale: scale.id
|
|
322
|
+
})
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
export function registerColorEncodingAction(ProgramClass) {
|
|
328
|
+
ProgramClass.prototype.encodeColor = encodeColor;
|
|
329
|
+
}
|