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,148 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
+
import { validateKeys } from "../../core/validation.js";
|
|
4
|
+
import {
|
|
5
|
+
validateOrdinalDomain,
|
|
6
|
+
validateOrdinalRange,
|
|
7
|
+
validateScaleDomain,
|
|
8
|
+
validateScaleRange,
|
|
9
|
+
validateScaleType,
|
|
10
|
+
validateDiscretizedColorDomain,
|
|
11
|
+
validateDiscretizedColorRange,
|
|
12
|
+
validateSequentialColorRange,
|
|
13
|
+
hasOrdinalDomain,
|
|
14
|
+
isColorScaleType,
|
|
15
|
+
isContinuousColorScaleType,
|
|
16
|
+
isDiscretizedColorScaleType,
|
|
17
|
+
normalizeScaleDefinition
|
|
18
|
+
} from "../../grammar/scales.js";
|
|
19
|
+
import { findSemanticScale } from "../../selectors/scales.js";
|
|
20
|
+
|
|
21
|
+
const CREATE_SCALE_OPTIONS = Object.freeze([
|
|
22
|
+
"id",
|
|
23
|
+
"type",
|
|
24
|
+
"domain",
|
|
25
|
+
"range",
|
|
26
|
+
"nice",
|
|
27
|
+
"zero",
|
|
28
|
+
"clamp",
|
|
29
|
+
"reverse",
|
|
30
|
+
"base",
|
|
31
|
+
"exponent",
|
|
32
|
+
"constant",
|
|
33
|
+
"paddingInner",
|
|
34
|
+
"paddingOuter",
|
|
35
|
+
"padding",
|
|
36
|
+
"align",
|
|
37
|
+
"palette",
|
|
38
|
+
"interpolate",
|
|
39
|
+
"unknown"
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
function validateOptions(args) {
|
|
43
|
+
validateKeys(args, CREATE_SCALE_OPTIONS, "createScale");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sameScaleSetting(left, right) {
|
|
47
|
+
if (left === right) return true;
|
|
48
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
49
|
+
return left.length === right.length && left.every(
|
|
50
|
+
(value, index) => sameScaleSetting(value, right[index])
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (
|
|
54
|
+
left !== null &&
|
|
55
|
+
right !== null &&
|
|
56
|
+
typeof left === "object" &&
|
|
57
|
+
typeof right === "object" &&
|
|
58
|
+
!Array.isArray(left) &&
|
|
59
|
+
!Array.isArray(right)
|
|
60
|
+
) {
|
|
61
|
+
const leftKeys = Object.keys(left);
|
|
62
|
+
const rightKeys = Object.keys(right);
|
|
63
|
+
return leftKeys.length === rightKeys.length && leftKeys.every(
|
|
64
|
+
key => Object.hasOwn(right, key) && sameScaleSetting(left[key], right[key])
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function assertEquivalentScale(existing, expected) {
|
|
71
|
+
const keys = new Set([...Object.keys(existing), ...Object.keys(expected)]);
|
|
72
|
+
keys.delete("id");
|
|
73
|
+
if ([...keys].some(key => !sameScaleSetting(existing[key], expected[key]))) {
|
|
74
|
+
throw new Error(`Scale "${existing.id}" already exists with a different definition.`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const createScale = action(
|
|
79
|
+
{
|
|
80
|
+
op: "createScale",
|
|
81
|
+
description: "Create a named semantic scale."
|
|
82
|
+
},
|
|
83
|
+
function (args = {}) {
|
|
84
|
+
validateOptions(args);
|
|
85
|
+
const id = validateUserId(args.id, "Scale id");
|
|
86
|
+
const type = validateScaleType(args.type ?? "linear");
|
|
87
|
+
const colorType = isColorScaleType(type);
|
|
88
|
+
if (args.palette !== undefined && args.range !== undefined) {
|
|
89
|
+
throw new Error("Color scale cannot specify both palette and range.");
|
|
90
|
+
}
|
|
91
|
+
if (args.palette !== undefined && !colorType) {
|
|
92
|
+
throw new Error(`Scale type "${type}" does not support palette.`);
|
|
93
|
+
}
|
|
94
|
+
if (args.interpolate !== undefined && type !== "sequential") {
|
|
95
|
+
throw new Error(`Scale type "${type}" does not support interpolate.`);
|
|
96
|
+
}
|
|
97
|
+
const requestedRange = args.palette === undefined
|
|
98
|
+
? args.range
|
|
99
|
+
: { palette: args.palette };
|
|
100
|
+
const definition = normalizeScaleDefinition({
|
|
101
|
+
type,
|
|
102
|
+
patch: {
|
|
103
|
+
...args,
|
|
104
|
+
...(requestedRange === undefined ? {} : { range: requestedRange })
|
|
105
|
+
},
|
|
106
|
+
validateDomain: (scaleType, value) =>
|
|
107
|
+
isDiscretizedColorScaleType(scaleType)
|
|
108
|
+
? validateDiscretizedColorDomain(scaleType, value)
|
|
109
|
+
: hasOrdinalDomain(scaleType)
|
|
110
|
+
? validateOrdinalDomain(value)
|
|
111
|
+
: validateScaleDomain(value),
|
|
112
|
+
validateRange: (scaleType, value) =>
|
|
113
|
+
isDiscretizedColorScaleType(scaleType)
|
|
114
|
+
? validateDiscretizedColorRange(value)
|
|
115
|
+
: isContinuousColorScaleType(scaleType)
|
|
116
|
+
? validateSequentialColorRange(value)
|
|
117
|
+
: scaleType === "ordinal"
|
|
118
|
+
? validateOrdinalRange(value)
|
|
119
|
+
: validateScaleRange(value)
|
|
120
|
+
});
|
|
121
|
+
if (Object.hasOwn(args, "unknown")) definition.unknown = args.unknown;
|
|
122
|
+
const existing = findSemanticScale(this, id);
|
|
123
|
+
|
|
124
|
+
if (existing !== undefined) {
|
|
125
|
+
assertEquivalentScale(existing, definition);
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let next = this
|
|
130
|
+
.editSemantic({ property: `scale[${id}].type`, value: definition.type })
|
|
131
|
+
.editSemantic({ property: `scale[${id}].domain`, value: definition.domain })
|
|
132
|
+
.editSemantic({ property: `scale[${id}].range`, value: definition.range });
|
|
133
|
+
|
|
134
|
+
for (const property of [
|
|
135
|
+
"nice", "zero", "clamp", "reverse", "base", "exponent", "constant",
|
|
136
|
+
"paddingInner", "paddingOuter", "padding", "align", "interpolate",
|
|
137
|
+
"unknown"
|
|
138
|
+
]) {
|
|
139
|
+
if (definition[property] === undefined) continue;
|
|
140
|
+
next = next.editSemantic({
|
|
141
|
+
property: `scale[${id}].${property}`,
|
|
142
|
+
value: definition[property]
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return next;
|
|
147
|
+
}
|
|
148
|
+
);
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
2
|
+
import { isPlainObject } from "../../core/immutable.js";
|
|
3
|
+
import { validateKeys } from "../../core/validation.js";
|
|
4
|
+
import {
|
|
5
|
+
validateColorRange,
|
|
6
|
+
validateContinuousColorInterpolation,
|
|
7
|
+
validateLinearScaleType,
|
|
8
|
+
validateOpacityRange,
|
|
9
|
+
validateOrdinalDomain,
|
|
10
|
+
validateOrdinalScaleType,
|
|
11
|
+
validateScaleDomain,
|
|
12
|
+
validateScaleRange,
|
|
13
|
+
validateShapeRange,
|
|
14
|
+
validateSizeRange,
|
|
15
|
+
validateStrokeDashRange,
|
|
16
|
+
validateSequentialColorRange,
|
|
17
|
+
validateDiscretizedColorDomain,
|
|
18
|
+
validateDiscretizedColorRange,
|
|
19
|
+
validateTimeScaleType,
|
|
20
|
+
normalizeScaleDefinition,
|
|
21
|
+
SCALE_ROLES,
|
|
22
|
+
validateScalePropertyForType,
|
|
23
|
+
validateScaleTypeForRole,
|
|
24
|
+
isDiscretePositionScaleType
|
|
25
|
+
} from "../../grammar/scales.js";
|
|
26
|
+
import { withScaleUnknown } from "../../grammar/scales.js";
|
|
27
|
+
import { findSemanticScale } from "../../selectors/scales.js";
|
|
28
|
+
|
|
29
|
+
const BASE_OPTIONS = Object.freeze(["id", "type", "domain", "range"]);
|
|
30
|
+
const POSITION_OPTIONS = Object.freeze([
|
|
31
|
+
...BASE_OPTIONS,
|
|
32
|
+
"nice",
|
|
33
|
+
"zero",
|
|
34
|
+
"clamp",
|
|
35
|
+
"reverse",
|
|
36
|
+
"base",
|
|
37
|
+
"exponent",
|
|
38
|
+
"constant",
|
|
39
|
+
"paddingInner",
|
|
40
|
+
"paddingOuter",
|
|
41
|
+
"padding",
|
|
42
|
+
"align",
|
|
43
|
+
"unknown"
|
|
44
|
+
]);
|
|
45
|
+
const COLOR_OPTIONS = Object.freeze([...BASE_OPTIONS, "palette", "unknown"]);
|
|
46
|
+
const SEQUENTIAL_COLOR_OPTIONS = Object.freeze([
|
|
47
|
+
...COLOR_OPTIONS,
|
|
48
|
+
"interpolate",
|
|
49
|
+
"clamp",
|
|
50
|
+
"reverse"
|
|
51
|
+
]);
|
|
52
|
+
const OPACITY_OPTIONS = Object.freeze([
|
|
53
|
+
...POSITION_OPTIONS,
|
|
54
|
+
"clamp",
|
|
55
|
+
"reverse"
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
function optionsObject(options) {
|
|
59
|
+
if (!isPlainObject(options)) {
|
|
60
|
+
throw new TypeError("Encoding scale must be a plain object.");
|
|
61
|
+
}
|
|
62
|
+
return options;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function resolvePositionScaleDefinition(
|
|
66
|
+
program,
|
|
67
|
+
channel,
|
|
68
|
+
fieldType,
|
|
69
|
+
options,
|
|
70
|
+
defaults = {}
|
|
71
|
+
) {
|
|
72
|
+
optionsObject(options);
|
|
73
|
+
validateKeys(options, POSITION_OPTIONS, "scale");
|
|
74
|
+
const id = validateUserId(options.id ?? channel, "Scale id");
|
|
75
|
+
const existing = findSemanticScale(program, id);
|
|
76
|
+
const expectedType = fieldType === "temporal"
|
|
77
|
+
? "time"
|
|
78
|
+
: ["ordinal", "nominal"].includes(fieldType)
|
|
79
|
+
? defaults.discreteType ?? "point"
|
|
80
|
+
: "linear";
|
|
81
|
+
const type = options.type ?? existing?.type ?? expectedType;
|
|
82
|
+
if (fieldType === "temporal") validateTimeScaleType(type);
|
|
83
|
+
else if (["ordinal", "nominal"].includes(fieldType)) {
|
|
84
|
+
if (!isDiscretePositionScaleType(type)) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Scale type "${type}" is not valid for discrete position.`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else validateScaleTypeForRole(type, SCALE_ROLES.quantitativePosition);
|
|
91
|
+
const scale = {
|
|
92
|
+
id,
|
|
93
|
+
...normalizeScaleDefinition({
|
|
94
|
+
type,
|
|
95
|
+
previous: existing,
|
|
96
|
+
patch: options,
|
|
97
|
+
defaults: {
|
|
98
|
+
...(existing === undefined && defaults.nice !== undefined
|
|
99
|
+
? { nice: defaults.nice }
|
|
100
|
+
: {}),
|
|
101
|
+
...(existing === undefined && defaults.zero !== undefined
|
|
102
|
+
? { zero: defaults.zero }
|
|
103
|
+
: {})
|
|
104
|
+
},
|
|
105
|
+
retainCoreOnTypeChange: true,
|
|
106
|
+
retainCompatibleOnTypeChange: true,
|
|
107
|
+
validateDomain: (_scaleType, value) =>
|
|
108
|
+
["ordinal", "nominal"].includes(fieldType)
|
|
109
|
+
? validateOrdinalDomain(value)
|
|
110
|
+
: validateScaleDomain(value),
|
|
111
|
+
validateRange: (_scaleType, value) => validateScaleRange(value)
|
|
112
|
+
})
|
|
113
|
+
};
|
|
114
|
+
return withScaleUnknown(scale, { ...existing, ...options }, channel);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function resolveColorScaleDefinition(program, options) {
|
|
118
|
+
optionsObject(options);
|
|
119
|
+
validateKeys(options, COLOR_OPTIONS, "scale");
|
|
120
|
+
if (options.palette !== undefined && options.range !== undefined) {
|
|
121
|
+
throw new Error("Color scale cannot specify both palette and range.");
|
|
122
|
+
}
|
|
123
|
+
const id = validateUserId(options.id ?? "color", "Scale id");
|
|
124
|
+
const existing = findSemanticScale(program, id);
|
|
125
|
+
const range = options.palette === undefined
|
|
126
|
+
? options.range
|
|
127
|
+
: { palette: options.palette };
|
|
128
|
+
return withScaleUnknown({
|
|
129
|
+
id,
|
|
130
|
+
type: validateOrdinalScaleType(options.type ?? existing?.type ?? "ordinal"),
|
|
131
|
+
domain: validateOrdinalDomain(options.domain ?? existing?.domain ?? "auto"),
|
|
132
|
+
range: validateColorRange(range ?? existing?.range ?? "auto")
|
|
133
|
+
}, { ...existing, ...options }, "color");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function continuousDomain(value, fieldType) {
|
|
137
|
+
if (value === "auto") return value;
|
|
138
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
139
|
+
throw new TypeError("Continuous color domain must contain two values or auto.");
|
|
140
|
+
}
|
|
141
|
+
const normalized = value.map(item => {
|
|
142
|
+
if (fieldType === "quantitative") return item;
|
|
143
|
+
return typeof item === "string" ? Date.parse(item) : item;
|
|
144
|
+
});
|
|
145
|
+
if (!normalized.every(Number.isFinite) || normalized[0] === normalized[1]) {
|
|
146
|
+
throw new TypeError("Continuous color domain requires two distinct valid values.");
|
|
147
|
+
}
|
|
148
|
+
return normalized;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function resolveSequentialColorScaleDefinition(
|
|
152
|
+
program,
|
|
153
|
+
fieldType,
|
|
154
|
+
options
|
|
155
|
+
) {
|
|
156
|
+
optionsObject(options);
|
|
157
|
+
validateKeys(options, SEQUENTIAL_COLOR_OPTIONS, "scale");
|
|
158
|
+
if (options.palette !== undefined && options.range !== undefined) {
|
|
159
|
+
throw new Error("Color scale cannot specify both palette and range.");
|
|
160
|
+
}
|
|
161
|
+
const id = validateUserId(options.id ?? "color", "Scale id");
|
|
162
|
+
const existing = findSemanticScale(program, id);
|
|
163
|
+
const type = options.type ?? existing?.type ?? "sequential";
|
|
164
|
+
if (type !== "sequential") {
|
|
165
|
+
throw new Error(`Unsupported continuous color scale type "${type}".`);
|
|
166
|
+
}
|
|
167
|
+
for (const property of ["clamp", "reverse"]) {
|
|
168
|
+
if (options[property] !== undefined && typeof options[property] !== "boolean") {
|
|
169
|
+
throw new TypeError(`Scale ${property} must be a boolean.`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const requestedRange = options.palette === undefined
|
|
173
|
+
? options.range
|
|
174
|
+
: { palette: options.palette };
|
|
175
|
+
const scale = {
|
|
176
|
+
id,
|
|
177
|
+
type,
|
|
178
|
+
domain: continuousDomain(options.domain ?? existing?.domain ?? "auto", fieldType),
|
|
179
|
+
range: validateSequentialColorRange(
|
|
180
|
+
requestedRange ?? existing?.range ?? { palette: "viridis" }
|
|
181
|
+
),
|
|
182
|
+
interpolate: validateContinuousColorInterpolation(
|
|
183
|
+
options.interpolate ?? existing?.interpolate ?? "rgb"
|
|
184
|
+
)
|
|
185
|
+
};
|
|
186
|
+
const clamp = options.clamp ?? existing?.clamp;
|
|
187
|
+
const reverse = options.reverse ?? existing?.reverse;
|
|
188
|
+
if (clamp !== undefined) scale.clamp = clamp;
|
|
189
|
+
if (reverse !== undefined) scale.reverse = reverse;
|
|
190
|
+
return withScaleUnknown(scale, { ...existing, ...options }, "color");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function resolveQuantitativeColorScaleDefinition(
|
|
194
|
+
program,
|
|
195
|
+
fieldType,
|
|
196
|
+
options
|
|
197
|
+
) {
|
|
198
|
+
optionsObject(options);
|
|
199
|
+
const type = options.type ?? findSemanticScale(
|
|
200
|
+
program,
|
|
201
|
+
options.id ?? "color"
|
|
202
|
+
)?.type ?? "sequential";
|
|
203
|
+
if (type === "sequential") {
|
|
204
|
+
return resolveSequentialColorScaleDefinition(program, fieldType, options);
|
|
205
|
+
}
|
|
206
|
+
validateKeys(options, [
|
|
207
|
+
...COLOR_OPTIONS,
|
|
208
|
+
"clamp",
|
|
209
|
+
"reverse"
|
|
210
|
+
], "scale");
|
|
211
|
+
if (fieldType !== "quantitative") {
|
|
212
|
+
throw new Error(`Scale type "${type}" requires quantitative color.`);
|
|
213
|
+
}
|
|
214
|
+
validateScaleTypeForRole(type, SCALE_ROLES.discretizedColor);
|
|
215
|
+
if (options.palette !== undefined && options.range !== undefined) {
|
|
216
|
+
throw new Error("Color scale cannot specify both palette and range.");
|
|
217
|
+
}
|
|
218
|
+
for (const property of ["clamp", "reverse"]) {
|
|
219
|
+
if (options[property] !== undefined && typeof options[property] !== "boolean") {
|
|
220
|
+
throw new TypeError(`Scale ${property} must be a boolean.`);
|
|
221
|
+
}
|
|
222
|
+
if (options[property] !== undefined) validateScalePropertyForType(type, property);
|
|
223
|
+
}
|
|
224
|
+
const id = validateUserId(options.id ?? "color", "Scale id");
|
|
225
|
+
const existing = findSemanticScale(program, id);
|
|
226
|
+
const requestedRange = options.palette === undefined
|
|
227
|
+
? options.range
|
|
228
|
+
: { palette: options.palette };
|
|
229
|
+
const scale = {
|
|
230
|
+
id,
|
|
231
|
+
type,
|
|
232
|
+
domain: validateDiscretizedColorDomain(
|
|
233
|
+
type,
|
|
234
|
+
options.domain ?? (existing?.type === type ? existing.domain : "auto")
|
|
235
|
+
),
|
|
236
|
+
range: validateDiscretizedColorRange(
|
|
237
|
+
requestedRange ?? (existing?.type === type ? existing.range : { palette: "viridis" })
|
|
238
|
+
)
|
|
239
|
+
};
|
|
240
|
+
const clamp = options.clamp ?? (existing?.type === type ? existing.clamp : undefined);
|
|
241
|
+
const reverse = options.reverse ?? (existing?.type === type ? existing.reverse : undefined);
|
|
242
|
+
if (clamp !== undefined) scale.clamp = clamp;
|
|
243
|
+
if (reverse !== undefined) scale.reverse = reverse;
|
|
244
|
+
return withScaleUnknown(scale, { ...existing, ...options }, "color");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function resolveStrokeDashScaleDefinition(program, options) {
|
|
248
|
+
optionsObject(options);
|
|
249
|
+
validateKeys(options, [...BASE_OPTIONS, "unknown"], "scale");
|
|
250
|
+
const id = validateUserId(options.id ?? "strokeDash", "Scale id");
|
|
251
|
+
const existing = findSemanticScale(program, id);
|
|
252
|
+
return withScaleUnknown({
|
|
253
|
+
id,
|
|
254
|
+
type: validateOrdinalScaleType(options.type ?? existing?.type ?? "ordinal"),
|
|
255
|
+
domain: validateOrdinalDomain(options.domain ?? existing?.domain ?? "auto"),
|
|
256
|
+
range: validateStrokeDashRange(options.range ?? existing?.range ?? "auto")
|
|
257
|
+
}, { ...existing, ...options }, "strokeDash");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function resolveAppearanceScaleDefinition(program, channel, options) {
|
|
261
|
+
optionsObject(options);
|
|
262
|
+
validateKeys(options, [...BASE_OPTIONS, "unknown"], "scale");
|
|
263
|
+
const id = validateUserId(options.id ?? channel, "Scale id");
|
|
264
|
+
const existing = findSemanticScale(program, id);
|
|
265
|
+
const shape = channel === "shape";
|
|
266
|
+
return withScaleUnknown({
|
|
267
|
+
id,
|
|
268
|
+
type: shape
|
|
269
|
+
? validateOrdinalScaleType(options.type ?? existing?.type ?? "ordinal")
|
|
270
|
+
: validateLinearScaleType(options.type ?? existing?.type ?? "linear"),
|
|
271
|
+
domain: shape
|
|
272
|
+
? validateOrdinalDomain(options.domain ?? existing?.domain ?? "auto")
|
|
273
|
+
: validateScaleDomain(options.domain ?? existing?.domain ?? "auto"),
|
|
274
|
+
range: shape
|
|
275
|
+
? validateShapeRange(options.range ?? existing?.range ?? "auto")
|
|
276
|
+
: validateSizeRange(options.range ?? existing?.range ?? "auto")
|
|
277
|
+
}, { ...existing, ...options }, channel);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function resolveOpacityScaleDefinition(program, options) {
|
|
281
|
+
optionsObject(options);
|
|
282
|
+
validateKeys(options, OPACITY_OPTIONS, "scale");
|
|
283
|
+
const id = validateUserId(options.id ?? "opacity", "Scale id");
|
|
284
|
+
const existing = findSemanticScale(program, id);
|
|
285
|
+
const type = validateLinearScaleType(options.type ?? existing?.type ?? "linear");
|
|
286
|
+
for (const property of ["nice", "zero", "clamp", "reverse"]) {
|
|
287
|
+
if (options[property] !== undefined && typeof options[property] !== "boolean") {
|
|
288
|
+
throw new TypeError(`Scale ${property} must be a boolean.`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const scale = {
|
|
292
|
+
id,
|
|
293
|
+
type,
|
|
294
|
+
domain: validateScaleDomain(options.domain ?? existing?.domain ?? "auto"),
|
|
295
|
+
range: validateOpacityRange(options.range ?? existing?.range ?? "auto")
|
|
296
|
+
};
|
|
297
|
+
const nice = options.nice ?? existing?.nice;
|
|
298
|
+
const zero = options.zero ?? existing?.zero;
|
|
299
|
+
const clamp = options.clamp ?? existing?.clamp;
|
|
300
|
+
const reverse = options.reverse ?? existing?.reverse;
|
|
301
|
+
if (nice !== undefined) scale.nice = nice;
|
|
302
|
+
if (zero !== undefined) scale.zero = zero;
|
|
303
|
+
if (clamp !== undefined) scale.clamp = clamp;
|
|
304
|
+
if (reverse !== undefined) scale.reverse = reverse;
|
|
305
|
+
return withScaleUnknown(scale, { ...existing, ...options }, "opacity");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function resolveOffsetScaleDefinition(program, options) {
|
|
309
|
+
optionsObject(options);
|
|
310
|
+
validateKeys(options, [...BASE_OPTIONS, "unknown"], "scale");
|
|
311
|
+
const id = validateUserId(options.id ?? "xOffset", "Scale id");
|
|
312
|
+
const existing = findSemanticScale(program, id);
|
|
313
|
+
return withScaleUnknown({
|
|
314
|
+
id,
|
|
315
|
+
type: validateOrdinalScaleType(options.type ?? existing?.type ?? "ordinal"),
|
|
316
|
+
domain: validateOrdinalDomain(options.domain ?? existing?.domain ?? "auto"),
|
|
317
|
+
range: validateScaleRange(options.range ?? existing?.range ?? "auto")
|
|
318
|
+
}, { ...existing, ...options }, "xOffset");
|
|
319
|
+
}
|