ggaction 0.0.9 → 0.0.11
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 +84 -0
- package/README.md +5 -4
- package/knowledge/action-card.schema.json +1 -0
- package/knowledge/action-cards.json +1 -10941
- package/knowledge/action-cards.schema.json +1 -0
- package/knowledge/extension-authoring.md +248 -0
- package/knowledge/intent-taxonomy.json +1 -183
- package/knowledge/intent-taxonomy.schema.json +1 -0
- package/knowledge/mcp-resources.json +1 -95
- package/knowledge/mcp-resources.schema.json +1 -0
- package/knowledge/task-packet.schema.json +1 -159
- package/knowledge/task-resolver.js +1 -1230
- package/package.json +19 -2
- package/src/actions/boxPlots/edit.js +45 -298
- package/src/actions/boxPlots/options.js +46 -77
- package/src/actions/boxPlots/resolve.js +1 -1
- package/src/actions/charts/bar.js +3 -0
- package/src/actions/charts/heatmap.js +73 -83
- package/src/actions/charts/index.js +1 -1
- package/src/actions/charts/parallel.js +35 -1
- package/src/actions/charts/shared.js +20 -0
- package/src/actions/composition/actions.js +46 -49
- package/src/actions/data/bin2d.js +105 -2
- package/src/actions/data/box.js +17 -17
- package/src/actions/data/density.js +42 -78
- package/src/actions/data/gradientProfile.js +22 -52
- package/src/actions/data/horizon.js +16 -41
- package/src/actions/data/index.js +10 -10
- package/src/actions/data/interval.js +16 -38
- package/src/actions/data/intervalEdit.js +55 -0
- package/src/actions/data/regression.js +7 -17
- package/src/actions/data/shared.js +47 -0
- package/src/actions/data/timeUnit.js +14 -38
- package/src/actions/data/window.js +14 -38
- package/src/actions/distributions/revision.js +154 -34
- package/src/actions/encodings/angle.js +9 -10
- package/src/actions/encodings/appearance.js +17 -51
- package/src/actions/encodings/density/resolve.js +3 -1
- package/src/actions/encodings/density.js +1 -15
- package/src/actions/encodings/horizon/resolve.js +13 -3
- package/src/actions/encodings/horizon.js +8 -7
- package/src/actions/encodings/index.js +9 -9
- package/src/actions/encodings/offset.js +8 -24
- package/src/actions/encodings/parallel.js +9 -10
- package/src/actions/encodings/pathOrder.js +6 -13
- package/src/actions/encodings/position/policies/index.js +4 -4
- package/src/actions/encodings/position/policies/line.js +47 -17
- package/src/actions/encodings/position/resolve.js +24 -5
- package/src/actions/encodings/ranged.js +18 -23
- package/src/actions/encodings/remove.js +15 -25
- package/src/actions/encodings/ruleAppearance.js +9 -24
- package/src/actions/encodings/shared.js +37 -0
- package/src/actions/encodings/strokeDash.js +8 -23
- package/src/actions/encodings/text.js +9 -10
- package/src/actions/errorBands/create.js +105 -39
- package/src/actions/errorBands/edit.js +89 -93
- package/src/actions/errorBands/index.js +1 -2
- package/src/actions/errorBars/create.js +78 -27
- package/src/actions/errorBars/edit.js +81 -48
- package/src/actions/errorBars/index.js +1 -2
- package/src/actions/facets/actions.js +45 -69
- package/src/actions/facets/derive.js +38 -53
- package/src/actions/facets/replay.js +0 -1
- package/src/actions/gradientPlots/components.js +59 -48
- package/src/actions/gradientPlots/edit.js +35 -216
- package/src/actions/gradientPlots/index.js +11 -10
- package/src/actions/gradientPlots/materialize.js +21 -35
- package/src/actions/gradientPlots/options.js +5 -7
- package/src/actions/gradientPlots/paint.js +9 -6
- package/src/actions/gradientPlots/resolve.js +1 -1
- package/src/actions/guides/axes/index.js +3 -3
- package/src/actions/guides/axes/labels.js +68 -26
- package/src/actions/guides/axes/lines.js +22 -2
- package/src/actions/guides/axes/policy.js +28 -8
- package/src/actions/guides/axes/ticks.js +27 -9
- package/src/actions/guides/axes/titles.js +113 -65
- package/src/actions/guides/grids/resolve.js +14 -0
- package/src/actions/guides/guides.js +2 -4
- package/src/actions/guides/index.js +2 -2
- package/src/actions/guides/legends/categorical/actions.js +44 -68
- package/src/actions/guides/legends/categorical/components.js +6 -1
- package/src/actions/guides/legends/categorical/index.js +2 -2
- package/src/actions/guides/legends/categorical/layout.js +148 -175
- package/src/actions/guides/legends/categorical/recipes.js +5 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -1
- package/src/actions/guides/legends/continuous/common.js +117 -66
- package/src/actions/guides/legends/continuous/gradient.js +38 -37
- package/src/actions/guides/legends/continuous/interval.js +97 -76
- package/src/actions/guides/legends/continuous/opacity.js +28 -38
- package/src/actions/guides/legends/edit.js +14 -1
- package/src/actions/guides/legends/index.js +1 -1
- package/src/actions/guides/legends/lane.js +2 -1
- package/src/actions/guides/legends/remove.js +8 -0
- package/src/actions/guides/legends/size.js +36 -43
- package/src/actions/guides/legends/strokeWidth.js +20 -26
- package/src/actions/guides/polar/axes/labels.js +17 -1
- package/src/actions/guides/polar/axes/shared.js +7 -1
- package/src/actions/guides/polar/axes/titles.js +19 -2
- package/src/actions/guides/polar/grids.js +4 -0
- package/src/actions/guides/polar/resolve.js +6 -0
- package/src/actions/index.js +13 -13
- package/src/actions/intervals/resolve.js +87 -117
- package/src/actions/marks/arc/actions.js +31 -36
- package/src/actions/marks/area/actions.js +23 -31
- package/src/actions/marks/index.js +6 -6
- package/src/actions/marks/line/actions.js +33 -52
- package/src/actions/marks/line/materialize.js +7 -5
- package/src/actions/marks/point/materialize.js +5 -3
- package/src/actions/marks/remove.js +21 -0
- package/src/actions/marks/rule/actions.js +53 -53
- package/src/actions/marks/shared.js +8 -4
- package/src/actions/marks/text/actions.js +14 -6
- package/src/actions/marks/text/layout.js +21 -45
- package/src/actions/marks/tick/actions.js +22 -21
- package/src/actions/primitives/createGraphics.js +4 -0
- package/src/actions/primitives/editGraphics.js +4 -0
- package/src/actions/primitives/index.js +3 -3
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +2 -2
- package/src/actions/primitives/semanticValidation/layer.js +1 -1
- package/src/actions/regression/edit.js +3 -43
- package/src/actions/scales/consumers/families.js +24 -4
- package/src/actions/scales/consumers/index.js +1 -1
- package/src/actions/scales/consumers/seriesLayout.js +17 -0
- package/src/actions/scales/definitions.js +100 -103
- package/src/actions/scales/materialize.js +5 -0
- package/src/actions/selection/actions.js +79 -121
- package/src/actions/titles/actions.js +48 -94
- package/src/actions/titles/resolve.js +14 -27
- package/src/actions/violinPlots/create.js +37 -44
- package/src/core/action.js +20 -5
- package/src/core/extensionRegistry.js +104 -0
- package/src/core/textMetrics.js +74 -37
- package/src/core/validation.js +18 -0
- package/src/extension.js +8 -1
- package/src/grammar/aggregate.js +48 -28
- package/src/grammar/arcs.js +58 -11
- package/src/grammar/areaSeries.js +14 -16
- package/src/grammar/bin2d.js +37 -23
- package/src/grammar/boxPlot.js +14 -13
- package/src/grammar/categoricalDensity.js +14 -14
- package/src/grammar/categoryOrder.js +8 -14
- package/src/grammar/curveCommands.js +87 -20
- package/src/grammar/density.js +162 -141
- package/src/grammar/facets/index.js +14 -0
- package/src/grammar/facets/scales.js +7 -4
- package/src/grammar/gradientProfile.js +12 -15
- package/src/grammar/histogram.js +145 -83
- package/src/grammar/horizon.js +60 -23
- package/src/grammar/interval.js +14 -13
- package/src/grammar/lineSeries.js +91 -61
- package/src/grammar/numeric.js +290 -0
- package/src/grammar/paint.js +3 -4
- package/src/grammar/palettes.js +21 -32
- package/src/grammar/pathCommands.js +6 -0
- package/src/grammar/polarPaths.js +5 -5
- package/src/grammar/regression/derive.js +48 -9
- package/src/grammar/regression/models.js +248 -69
- package/src/grammar/regression/parameters.js +8 -0
- package/src/grammar/scales/color.js +35 -26
- package/src/grammar/scales/continuous.js +71 -24
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/discretized.js +42 -12
- package/src/grammar/scales/fields.js +21 -4
- package/src/grammar/scales/ordinal.js +117 -61
- package/src/grammar/scales/temporal.js +40 -11
- package/src/grammar/scales/transformed.js +199 -52
- package/src/grammar/schemas/graphicBounds.js +270 -79
- package/src/grammar/seriesLayout.js +50 -18
- package/src/grammar/ticks.js +123 -53
- package/src/grammar/timeUnit.js +18 -8
- package/src/grammar/transforms.js +4 -1
- package/src/grammar/window.js +70 -57
- package/src/index.js +1 -1
- package/src/layout/canvas.js +4 -8
- package/src/layout/composition.js +39 -40
- package/src/layout/facets.js +10 -14
- package/src/layout/labels.js +91 -15
- package/src/layout/legend.js +22 -20
- package/src/layout/legendLane.js +54 -40
- package/src/layout/title.js +7 -7
- package/src/materialization/bars/aggregate.js +14 -40
- package/src/materialization/bars/grouped.js +7 -18
- package/src/materialization/bars/histogram.js +2 -19
- package/src/materialization/bars/ranged.js +20 -15
- package/src/materialization/bars/resolve.js +21 -0
- package/src/materialization/facetGuides/legacyCategorical.js +2 -1
- package/src/materialization/facetGuides/placement.js +34 -19
- package/src/materialization/facetGuides/preparation.js +9 -5
- package/src/materialization/facets.js +39 -27
- package/src/materialization/guides/resources.js +2 -1
- package/src/materialization/marks/capabilities.js +5 -2
- package/src/materialization/marks/index.js +24 -4
- package/src/materialization/scales/policies/bar.js +61 -59
- package/src/materialization/scales/policies/binnedPosition.js +34 -0
- package/src/materialization/scales/policies/series.js +13 -7
- package/src/materialization/scales/resolve.js +4 -5
- package/src/materialization/selection/items/arc.js +16 -2
- package/src/materialization/selection/items/bar.js +4 -2
- package/src/materialization/selection/items/common.js +21 -1
- package/src/materialization/selection/items/path.js +19 -2
- package/src/materialization/selection/items/point.js +4 -16
- package/src/materialization/selection/items/tick.js +13 -21
- package/src/materialization/selection/point.js +11 -4
- package/src/materialization/selection/styles.js +19 -27
- package/src/materialization/text.js +4 -1
- package/src/mcp/adapter.js +8 -3
- package/src/renderers/canvas/fill.js +2 -2
- package/src/renderers/canvas/index.js +35 -2
- package/src/renderers/canvas/native.js +160 -0
- package/src/renderers/pdf.js +14 -0
- package/src/renderers/svg.js +37 -4
- package/types/extension.d.ts +19 -0
- package/types/index.d.ts +3 -0
- package/types/program.d.ts +509 -131
- package/types/svg.d.ts +1 -0
- package/src/actions/errorBands/components.js +0 -81
- package/src/actions/errorBands/options.js +0 -39
- package/src/actions/errorBands/resolve.js +0 -28
- package/src/actions/errorBars/components.js +0 -62
- package/src/actions/errorBars/options.js +0 -58
- package/src/actions/errorBars/resolve.js +0 -25
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "../../../../grammar/polarGuides.js";
|
|
7
7
|
import { resolvePlotGraphicPlacement } from
|
|
8
8
|
"../../../../materialization/graphicHierarchy.js";
|
|
9
|
+
import { assertPolarTextLayout } from "../../../../layout/labels.js";
|
|
9
10
|
import {
|
|
10
11
|
formatPolarGuideValues,
|
|
11
12
|
mapPolarGuideValues,
|
|
@@ -33,7 +34,7 @@ function labelGeometry(program, kind, config) {
|
|
|
33
34
|
const frame = resolvePolarFrameForProgram(program);
|
|
34
35
|
const mapped = mapPolarGuideValues(program, config);
|
|
35
36
|
const text = formatPolarGuideValues(program, config, mapped.values);
|
|
36
|
-
|
|
37
|
+
const geometry = {
|
|
37
38
|
values: mapped.values,
|
|
38
39
|
text,
|
|
39
40
|
...(kind === "theta"
|
|
@@ -49,6 +50,21 @@ function labelGeometry(program, kind, config) {
|
|
|
49
50
|
offset: config.offset
|
|
50
51
|
}))
|
|
51
52
|
};
|
|
53
|
+
assertPolarTextLayout({
|
|
54
|
+
canvas: program.graphicSpec.objects.canvas,
|
|
55
|
+
label: `${prefix(kind)} axis labels`,
|
|
56
|
+
items: text.map((value, index) => ({
|
|
57
|
+
x: geometry.x[index],
|
|
58
|
+
y: geometry.y[index],
|
|
59
|
+
text: value,
|
|
60
|
+
fontSize: config.fontSize,
|
|
61
|
+
fontFamily: config.fontFamily,
|
|
62
|
+
fontWeight: config.fontWeight,
|
|
63
|
+
textAlign: geometry.textAlign[index],
|
|
64
|
+
textBaseline: geometry.textBaseline[index]
|
|
65
|
+
}))
|
|
66
|
+
});
|
|
67
|
+
return geometry;
|
|
52
68
|
}
|
|
53
69
|
|
|
54
70
|
function resolveLabelConfig(program, kind, args, resources, previous) {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
validateGeneratedItemLimit,
|
|
3
|
+
validateOptionObject
|
|
4
|
+
} from "../../../../core/validation.js";
|
|
2
5
|
import {
|
|
3
6
|
POLAR_AXIS_DEFAULTS,
|
|
4
7
|
resolvePolarGuideResources
|
|
@@ -49,6 +52,9 @@ export function validateModeOptions(args, operation) {
|
|
|
49
52
|
if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
|
|
50
53
|
throw new Error(`${operation} cannot use count and values together.`);
|
|
51
54
|
}
|
|
55
|
+
if (Array.isArray(args.values)) {
|
|
56
|
+
validateGeneratedItemLimit(args.values.length, "Polar guide value count");
|
|
57
|
+
}
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
export function validateAngle(value) {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "../../../../grammar/polarGuides.js";
|
|
10
10
|
import { resolvePlotGraphicPlacement } from
|
|
11
11
|
"../../../../materialization/graphicHierarchy.js";
|
|
12
|
+
import { assertPolarTextLayout } from "../../../../layout/labels.js";
|
|
12
13
|
import { inferAxisTitleText } from "../../axes/titles.js";
|
|
13
14
|
import {
|
|
14
15
|
POLAR_AXIS_DEFAULTS,
|
|
@@ -39,6 +40,20 @@ function titleGeometry(program, kind, config) {
|
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
function validateTitleGeometry(program, kind, config, geometry, text) {
|
|
44
|
+
assertPolarTextLayout({
|
|
45
|
+
canvas: program.graphicSpec.objects.canvas,
|
|
46
|
+
label: `${prefix(kind)} axis title`,
|
|
47
|
+
items: [{
|
|
48
|
+
...geometry,
|
|
49
|
+
text,
|
|
50
|
+
fontSize: config.fontSize,
|
|
51
|
+
fontFamily: config.fontFamily,
|
|
52
|
+
fontWeight: config.fontWeight
|
|
53
|
+
}]
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
function resolveTitleConfig(kind, args, resources, previous) {
|
|
43
58
|
const config = {
|
|
44
59
|
...(previous ?? {}),
|
|
@@ -96,6 +111,8 @@ function makeEditTitle(kind) {
|
|
|
96
111
|
: config.inferredText
|
|
97
112
|
? inferAxisTitleText(this, polarGuideNames(kind).channel, config.scale)
|
|
98
113
|
: this.semanticSpec.guides.axis?.[kind]?.title;
|
|
114
|
+
const geometry = titleGeometry(this, kind, config);
|
|
115
|
+
validateTitleGeometry(this, kind, config, geometry, text);
|
|
99
116
|
let next = this;
|
|
100
117
|
if (text !== this.semanticSpec.guides.axis?.[kind]?.title) {
|
|
101
118
|
next = next.editSemantic({
|
|
@@ -103,7 +120,6 @@ function makeEditTitle(kind) {
|
|
|
103
120
|
value: text
|
|
104
121
|
});
|
|
105
122
|
}
|
|
106
|
-
const geometry = titleGeometry(next, kind, config);
|
|
107
123
|
next = next._withGuideConfig(kind, "title", config);
|
|
108
124
|
const properties = {
|
|
109
125
|
...geometry,
|
|
@@ -138,7 +154,8 @@ function makeCreateTitle(kind) {
|
|
|
138
154
|
args.text ?? inferAxisTitleText(this, names.channel, resources.scale),
|
|
139
155
|
"Polar axis title text"
|
|
140
156
|
);
|
|
141
|
-
titleGeometry(this, kind, config);
|
|
157
|
+
const geometry = titleGeometry(this, kind, config);
|
|
158
|
+
validateTitleGeometry(this, kind, config, geometry, text);
|
|
142
159
|
let next = withAxisSemantics(this, kind, resources);
|
|
143
160
|
if (kind === "radius" &&
|
|
144
161
|
next.guideConfigs.axis?.radius?.layout === undefined) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { action } from "../../../core/action.js";
|
|
2
2
|
import {
|
|
3
3
|
noOptions,
|
|
4
|
+
validateGeneratedItemLimit,
|
|
4
5
|
validateOptionObject,
|
|
5
6
|
validateNonEmptyString,
|
|
6
7
|
validateNonNegativeFinite
|
|
@@ -55,6 +56,9 @@ function validateOptions(args, operation, create) {
|
|
|
55
56
|
if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
|
|
56
57
|
throw new Error(`${operation} cannot use count and values together.`);
|
|
57
58
|
}
|
|
59
|
+
if (Array.isArray(args.values)) {
|
|
60
|
+
validateGeneratedItemLimit(args.values.length, "Polar grid value count");
|
|
61
|
+
}
|
|
58
62
|
if (Object.hasOwn(args, "color")) {
|
|
59
63
|
validateNonEmptyString(args.color, "Polar grid color");
|
|
60
64
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
2
2
|
import {
|
|
3
|
+
validateGeneratedItemLimit,
|
|
3
4
|
validateNonEmptyString,
|
|
4
5
|
validateNonNegativeFinite,
|
|
5
6
|
validatePositiveFinite
|
|
@@ -174,6 +175,7 @@ export function resolvePolarTickValues(program, config) {
|
|
|
174
175
|
if (config.mode !== "values") {
|
|
175
176
|
throw new Error("Discrete theta guides require explicit or inferred values.");
|
|
176
177
|
}
|
|
178
|
+
validateGeneratedItemLimit(config.values.length, "Polar guide value count");
|
|
177
179
|
const domain = new Set(scale.domain);
|
|
178
180
|
if (!config.values.every(value => domain.has(value))) {
|
|
179
181
|
throw new RangeError("Polar guide values must be inside the scale domain.");
|
|
@@ -181,6 +183,7 @@ export function resolvePolarTickValues(program, config) {
|
|
|
181
183
|
return config.values;
|
|
182
184
|
}
|
|
183
185
|
const values = valuesFromTickConfig(program, config);
|
|
186
|
+
validateGeneratedItemLimit(values.length, "Polar guide value count");
|
|
184
187
|
const low = Math.min(...scale.domain);
|
|
185
188
|
const high = Math.max(...scale.domain);
|
|
186
189
|
if (!values.every(value => value >= low && value <= high)) {
|
|
@@ -238,6 +241,9 @@ export function validatePolarTickConfig(config, label) {
|
|
|
238
241
|
(!Number.isInteger(config.count) || config.count <= 0)) {
|
|
239
242
|
throw new RangeError(`${label} count must be a positive integer.`);
|
|
240
243
|
}
|
|
244
|
+
if (config.mode === "count") {
|
|
245
|
+
validateGeneratedItemLimit(config.count, `${label} count`);
|
|
246
|
+
}
|
|
241
247
|
if (config.mode === "values" && (
|
|
242
248
|
!Array.isArray(config.values) || config.values.length === 0 ||
|
|
243
249
|
!config.values.every(value =>
|
package/src/actions/index.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { registerCategoryOrderActions } from "./categoryOrder/index.js";
|
|
4
|
-
import { registerCoordinateActions } from "./coordinates/actions.js";
|
|
5
|
-
import { registerCompositionActions } from "./composition/index.js";
|
|
1
|
+
import { registerPrimitiveActions } from "./primitives/index.js";
|
|
2
|
+
import { registerSelectionActions } from "./selection/index.js";
|
|
6
3
|
import { registerDataActions } from "./data/index.js";
|
|
4
|
+
import { registerCategoryOrderActions } from "./categoryOrder/index.js";
|
|
7
5
|
import { registerEncodingActions } from "./encodings/index.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
6
|
+
import { registerScaleActions } from "./scales/index.js";
|
|
7
|
+
import { registerMarkActions } from "./marks/index.js";
|
|
8
|
+
import { registerCoordinateActions } from "./coordinates/actions.js";
|
|
10
9
|
import { registerFacetActions } from "./facets/index.js";
|
|
11
|
-
import {
|
|
10
|
+
import { registerCompositionActions } from "./composition/index.js";
|
|
11
|
+
import { registerTitleActions } from "./titles/index.js";
|
|
12
|
+
import { registerCanvasActions } from "./canvas/index.js";
|
|
12
13
|
import { registerGuideActions } from "./guides/index.js";
|
|
13
14
|
import { registerGradientPlotActions } from "./gradientPlots/index.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
15
|
+
import { registerBoxPlotActions } from "./boxPlots/index.js";
|
|
16
|
+
import { registerErrorBarActions } from "./errorBars/index.js";
|
|
16
17
|
import { registerRegressionActions } from "./regression/index.js";
|
|
17
|
-
import { registerScaleActions } from "./scales/index.js";
|
|
18
|
-
import { registerTitleActions } from "./titles/index.js";
|
|
19
|
-
import { registerSelectionActions } from "./selection/index.js";
|
|
20
18
|
import { registerViolinPlotActions } from "./violinPlots/index.js";
|
|
19
|
+
import { registerErrorBandActions } from "./errorBands/index.js";
|
|
20
|
+
import { registerChartActions } from "./charts/index.js";
|
|
21
21
|
|
|
22
22
|
export function registerActions(ProgramClass) {
|
|
23
23
|
registerPrimitiveActions(ProgramClass);
|
|
@@ -3,37 +3,28 @@ import {
|
|
|
3
3
|
resolveOptionalUserId,
|
|
4
4
|
validateUserId
|
|
5
5
|
} from "../../core/identifiers.js";
|
|
6
|
+
import {
|
|
7
|
+
validateNonEmptyString as requireField,
|
|
8
|
+
validateOptionObject
|
|
9
|
+
} from "../../core/validation.js";
|
|
6
10
|
import { readQuantitativeField } from "../../grammar/scales/index.js";
|
|
7
11
|
import { findDataset } from "../../selectors/datasets.js";
|
|
8
12
|
import { hasLayer, resolveEligibleLayer } from "../../selectors/layers.js";
|
|
9
13
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
10
14
|
|
|
11
|
-
const CHANNEL_OPTIONS =
|
|
15
|
+
const CHANNEL_OPTIONS = [
|
|
12
16
|
"field", "fieldType", "scale", "center", "extent", "level", "lower", "upper"
|
|
13
|
-
]
|
|
14
|
-
const INTERVAL_PARAMETER_KEYS =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const FIELD_TYPES = Object.freeze([
|
|
17
|
+
];
|
|
18
|
+
const INTERVAL_PARAMETER_KEYS = CHANNEL_OPTIONS.slice(3);
|
|
19
|
+
const POSITION_CHANNELS = ["x", "y"];
|
|
20
|
+
const FIELD_TYPES = [
|
|
18
21
|
"quantitative", "temporal", "ordinal", "nominal"
|
|
19
|
-
]
|
|
22
|
+
];
|
|
20
23
|
|
|
21
24
|
function requireObject(value, label) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
const unknown = Object.keys(value).find(key => !CHANNEL_OPTIONS.includes(key));
|
|
26
|
-
if (unknown !== undefined) {
|
|
27
|
-
throw new Error(`Unknown ${label} option "${unknown}".`);
|
|
28
|
-
}
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function requireField(value, label) {
|
|
33
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
34
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
35
|
-
}
|
|
36
|
-
return value;
|
|
25
|
+
return validateOptionObject(value, CHANNEL_OPTIONS, label, {
|
|
26
|
+
plainObjectMessage: `${label} must be a plain object.`
|
|
27
|
+
});
|
|
37
28
|
}
|
|
38
29
|
|
|
39
30
|
function hasCompleteFieldPositions(layer) {
|
|
@@ -42,65 +33,30 @@ function hasCompleteFieldPositions(layer) {
|
|
|
42
33
|
return (
|
|
43
34
|
typeof layer.data === "string" &&
|
|
44
35
|
typeof layer.coordinate === "string" &&
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
FIELD_TYPES.includes(y.fieldType)
|
|
36
|
+
[x, y].every(encoding =>
|
|
37
|
+
typeof encoding?.field === "string" &&
|
|
38
|
+
typeof encoding.scale === "string" &&
|
|
39
|
+
FIELD_TYPES.includes(encoding.fieldType)
|
|
40
|
+
)
|
|
51
41
|
);
|
|
52
42
|
}
|
|
53
43
|
|
|
54
|
-
function resolveSourceLayer(program, args, operation) {
|
|
55
|
-
if (args.target === undefined && args.x !== undefined && args.y !== undefined) {
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
const target = args.target === undefined
|
|
59
|
-
? undefined
|
|
60
|
-
: validateUserId(args.target, `${operation} source layer id`);
|
|
61
|
-
return resolveEligibleLayer(program, {
|
|
62
|
-
target,
|
|
63
|
-
predicate: hasCompleteFieldPositions,
|
|
64
|
-
label: operation
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function resolveDataset(program, args, sourceLayer, operation, resourceLabel) {
|
|
69
|
-
const requested = args.data ?? sourceLayer?.data ?? program.context.currentData;
|
|
70
|
-
let dataset;
|
|
71
|
-
if (requested !== undefined) {
|
|
72
|
-
const id = validateUserId(requested, `${resourceLabel} dataset id`);
|
|
73
|
-
dataset = findDataset(program, id);
|
|
74
|
-
if (dataset === undefined) {
|
|
75
|
-
throw new Error(`Unknown ${resourceLabel} dataset "${id}".`);
|
|
76
|
-
}
|
|
77
|
-
} else if (program.semanticSpec.datasets.length === 1) {
|
|
78
|
-
dataset = program.semanticSpec.datasets[0];
|
|
79
|
-
}
|
|
80
|
-
if (dataset === undefined) {
|
|
81
|
-
throw new Error(`${operation} requires data or one uniquely inferable dataset.`);
|
|
82
|
-
}
|
|
83
|
-
if (sourceLayer !== undefined && dataset.id !== sourceLayer.data) {
|
|
84
|
-
throw new Error(
|
|
85
|
-
`${operation} data must match source layer "${sourceLayer.id}" data "${sourceLayer.data}".`
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
return dataset;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
44
|
function scaleOptions(program, value, inferredId, defaults = {}) {
|
|
92
45
|
if (value !== undefined && !isPlainObject(value)) {
|
|
93
46
|
throw new TypeError("Interval scale must be a plain object.");
|
|
94
47
|
}
|
|
95
48
|
const id = value?.id ?? inferredId;
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
49
|
+
const scale = {
|
|
50
|
+
...(id !== undefined && findSemanticScale(program, id) !== undefined
|
|
51
|
+
? {}
|
|
52
|
+
: defaults),
|
|
99
53
|
...(value ?? {}),
|
|
100
54
|
...(value?.id === undefined && inferredId !== undefined
|
|
101
55
|
? { id: inferredId }
|
|
102
56
|
: {})
|
|
103
57
|
};
|
|
58
|
+
if (value?.type === "log" && value.zero === undefined) delete scale.zero;
|
|
59
|
+
return scale;
|
|
104
60
|
}
|
|
105
61
|
|
|
106
62
|
function hasAny(value, keys) {
|
|
@@ -113,30 +69,28 @@ function resolveIntervalChannel(channels, sourceLayer, {
|
|
|
113
69
|
defaultIntervalChannel,
|
|
114
70
|
ambiguousMessage
|
|
115
71
|
}) {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
72
|
+
for (const hints of [
|
|
73
|
+
["lower", "upper"],
|
|
74
|
+
["center", "extent", "level"]
|
|
75
|
+
]) {
|
|
76
|
+
const hinted = POSITION_CHANNELS.filter(channel =>
|
|
77
|
+
hasAny(channels[channel], hints)
|
|
78
|
+
);
|
|
79
|
+
if (hinted.length > 1) {
|
|
80
|
+
throw new Error(`${operation} requires exactly one interval channel.`);
|
|
81
|
+
}
|
|
82
|
+
if (hinted.length === 1) return hinted[0];
|
|
121
83
|
}
|
|
122
|
-
if (explicitBounds.length === 1) return explicitBounds[0];
|
|
123
84
|
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
throw new Error(`${operation} requires exactly one interval channel.`);
|
|
85
|
+
const effectiveTypes = {};
|
|
86
|
+
for (const channel of POSITION_CHANNELS) {
|
|
87
|
+
effectiveTypes[channel] = channels[channel]?.fieldType ??
|
|
88
|
+
sourceLayer?.encoding?.[channel]?.fieldType;
|
|
129
89
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const effectiveTypes = Object.fromEntries(["x", "y"].map(channel => [
|
|
133
|
-
channel,
|
|
134
|
-
channels[channel]?.fieldType ?? sourceLayer?.encoding?.[channel]?.fieldType
|
|
135
|
-
]));
|
|
136
|
-
const quantitative = ["x", "y"].filter(channel =>
|
|
90
|
+
const quantitative = POSITION_CHANNELS.filter(channel =>
|
|
137
91
|
effectiveTypes[channel] === "quantitative"
|
|
138
92
|
);
|
|
139
|
-
const positional =
|
|
93
|
+
const positional = POSITION_CHANNELS.filter(channel =>
|
|
140
94
|
positionTypes.includes(effectiveTypes[channel])
|
|
141
95
|
);
|
|
142
96
|
if (quantitative.length === 1) {
|
|
@@ -224,10 +178,11 @@ function resolveInterval(program, channel, explicit, inferred, dataset, {
|
|
|
224
178
|
lower: requireField(explicit.lower, `${operation} ${channel} lower`),
|
|
225
179
|
upper: requireField(explicit.upper, `${operation} ${channel} upper`)
|
|
226
180
|
};
|
|
227
|
-
|
|
181
|
+
const fieldValues = Object.values(fields);
|
|
182
|
+
if (new Set(fieldValues).size !== 3) {
|
|
228
183
|
throw new Error("Explicit interval center, lower, and upper fields must be distinct.");
|
|
229
184
|
}
|
|
230
|
-
for (const field of
|
|
185
|
+
for (const field of fieldValues) {
|
|
231
186
|
readQuantitativeField(dataset.values, field);
|
|
232
187
|
}
|
|
233
188
|
return { channel, mode: "explicit", fields, scale, title: fields.center };
|
|
@@ -268,39 +223,46 @@ function resolveGrouping(args, sourceLayer, independentField, mode, {
|
|
|
268
223
|
};
|
|
269
224
|
}
|
|
270
225
|
|
|
271
|
-
function resolveOwner(program, requested, {
|
|
272
|
-
defaultId,
|
|
273
|
-
ownerLabel,
|
|
274
|
-
operation
|
|
275
|
-
}) {
|
|
276
|
-
const defaultOccupied = hasLayer(program, defaultId) ||
|
|
277
|
-
program.graphicSpec.objects[defaultId] !== undefined ||
|
|
278
|
-
findDataset(program, `${defaultId}IntervalData`) !== undefined;
|
|
279
|
-
return resolveOptionalUserId(requested, {
|
|
280
|
-
defaultId,
|
|
281
|
-
label: ownerLabel,
|
|
282
|
-
operation,
|
|
283
|
-
ambiguous: defaultOccupied
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
|
|
287
226
|
export function resolveIntervalComposite(program, args, policy) {
|
|
227
|
+
const { operation, resourceLabel } = policy;
|
|
288
228
|
const channels = {
|
|
289
229
|
x: args.x === undefined
|
|
290
230
|
? undefined
|
|
291
|
-
: requireObject(args.x, `${
|
|
231
|
+
: requireObject(args.x, `${operation} x`),
|
|
292
232
|
y: args.y === undefined
|
|
293
233
|
? undefined
|
|
294
|
-
: requireObject(args.y, `${
|
|
234
|
+
: requireObject(args.y, `${operation} y`)
|
|
295
235
|
};
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
236
|
+
let sourceLayer;
|
|
237
|
+
if (!(args.target === undefined && args.x !== undefined && args.y !== undefined)) {
|
|
238
|
+
const target = args.target === undefined
|
|
239
|
+
? undefined
|
|
240
|
+
: validateUserId(args.target, `${operation} source layer id`);
|
|
241
|
+
sourceLayer = resolveEligibleLayer(program, {
|
|
242
|
+
target,
|
|
243
|
+
predicate: hasCompleteFieldPositions,
|
|
244
|
+
label: operation
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const requested = args.data ?? sourceLayer?.data ?? program.context.currentData;
|
|
248
|
+
let dataset;
|
|
249
|
+
if (requested !== undefined) {
|
|
250
|
+
const dataId = validateUserId(requested, `${resourceLabel} dataset id`);
|
|
251
|
+
dataset = findDataset(program, dataId);
|
|
252
|
+
if (dataset === undefined) {
|
|
253
|
+
throw new Error(`Unknown ${resourceLabel} dataset "${dataId}".`);
|
|
254
|
+
}
|
|
255
|
+
} else if (program.semanticSpec.datasets.length === 1) {
|
|
256
|
+
dataset = program.semanticSpec.datasets[0];
|
|
257
|
+
}
|
|
258
|
+
if (dataset === undefined) {
|
|
259
|
+
throw new Error(`${operation} requires data or one uniquely inferable dataset.`);
|
|
260
|
+
}
|
|
261
|
+
if (sourceLayer !== undefined && dataset.id !== sourceLayer.data) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`${operation} data must match source layer "${sourceLayer.id}" data "${sourceLayer.data}".`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
304
266
|
const intervalChannel = resolveIntervalChannel(channels, sourceLayer, policy);
|
|
305
267
|
const positionChannel = intervalChannel === "x" ? "y" : "x";
|
|
306
268
|
const position = resolvePosition(
|
|
@@ -318,7 +280,15 @@ export function resolveIntervalComposite(program, args, policy) {
|
|
|
318
280
|
dataset,
|
|
319
281
|
policy
|
|
320
282
|
);
|
|
321
|
-
const
|
|
283
|
+
const defaultId = policy.defaultId;
|
|
284
|
+
const id = resolveOptionalUserId(args.id, {
|
|
285
|
+
defaultId,
|
|
286
|
+
label: policy.ownerLabel,
|
|
287
|
+
operation,
|
|
288
|
+
ambiguous: hasLayer(program, defaultId) ||
|
|
289
|
+
program.graphicSpec.objects[defaultId] !== undefined ||
|
|
290
|
+
findDataset(program, `${defaultId}IntervalData`) !== undefined
|
|
291
|
+
});
|
|
322
292
|
const generatedFields = {
|
|
323
293
|
center: `__${id}_center`,
|
|
324
294
|
lower: `__${id}_lower`,
|
|
@@ -340,7 +310,7 @@ export function resolveIntervalComposite(program, args, policy) {
|
|
|
340
310
|
dataId: interval.mode === "statistical" ? `${id}IntervalData` : dataset.id,
|
|
341
311
|
coordinate: validateUserId(
|
|
342
312
|
args.coordinate ?? sourceLayer?.coordinate ?? "main",
|
|
343
|
-
`${
|
|
313
|
+
`${resourceLabel} coordinate id`
|
|
344
314
|
),
|
|
345
315
|
orientation: intervalChannel === "y" ? "vertical" : "horizontal",
|
|
346
316
|
position,
|
|
@@ -10,7 +10,10 @@ import { buildAnnularSectorCommands } from "../../../grammar/polarPaths.js";
|
|
|
10
10
|
import { resolvePolarFrame } from "../../../grammar/polar.js";
|
|
11
11
|
import { mapOrdinalValues } from "../../../grammar/scales/index.js";
|
|
12
12
|
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
canMaterializeArc,
|
|
15
|
+
getSourceDependentMarkSteps
|
|
16
|
+
} from "../../../materialization/marks/index.js";
|
|
14
17
|
import { findDataset } from "../../../selectors/datasets.js";
|
|
15
18
|
import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
16
19
|
import { DEFAULT_COLORS } from "../../../theme/defaults.js";
|
|
@@ -20,6 +23,7 @@ import { rematerializeHighlightBaseline } from "../lifecycle.js";
|
|
|
20
23
|
import {
|
|
21
24
|
assertMarkAvailable,
|
|
22
25
|
applyLayeredMarkInheritance,
|
|
26
|
+
editMarkGraphic,
|
|
23
27
|
materializeInheritedMark,
|
|
24
28
|
resolveLayeredMarkInheritance,
|
|
25
29
|
resolveMarkData,
|
|
@@ -27,14 +31,11 @@ import {
|
|
|
27
31
|
validateMarkOptions
|
|
28
32
|
} from "../shared.js";
|
|
29
33
|
|
|
30
|
-
const
|
|
31
|
-
"
|
|
32
|
-
"strokeWidth"
|
|
33
|
-
]);
|
|
34
|
-
const EDIT_OPTIONS = Object.freeze([
|
|
35
|
-
"target", "innerRadius", "padAngle", "fill", "opacity", "stroke",
|
|
36
|
-
"strokeWidth"
|
|
34
|
+
const ARC_OPTIONS = Object.freeze([
|
|
35
|
+
"innerRadius", "padAngle", "fill", "opacity", "stroke", "strokeWidth"
|
|
37
36
|
]);
|
|
37
|
+
const CREATE_OPTIONS = Object.freeze(["id", "data", ...ARC_OPTIONS]);
|
|
38
|
+
const EDIT_OPTIONS = Object.freeze(["target", ...ARC_OPTIONS]);
|
|
38
39
|
const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
|
|
39
40
|
|
|
40
41
|
function validateInnerRadius(value) {
|
|
@@ -170,15 +171,15 @@ const rematerializeArcMark = action(
|
|
|
170
171
|
resolved = resolved.rematerializeScale({ id: colorScaleId });
|
|
171
172
|
}
|
|
172
173
|
const config = resolved.markConfigs[id] ?? {};
|
|
174
|
+
const frame = resolvePolarFrame(resolveGraphicBounds(resolved));
|
|
173
175
|
const derived = deriveArcSectors(dataset.values, layer, {
|
|
174
176
|
thetaScale: resolved.resolvedScales[thetaScaleId],
|
|
175
177
|
...(radiusScaleId === undefined
|
|
176
178
|
? {}
|
|
177
179
|
: { radiusScale: resolved.resolvedScales[radiusScaleId] }),
|
|
178
|
-
frame
|
|
180
|
+
frame,
|
|
179
181
|
innerRadiusRatio: config.innerRadius ?? 0
|
|
180
182
|
});
|
|
181
|
-
const frame = resolvePolarFrame(resolveGraphicBounds(resolved));
|
|
182
183
|
const commands = derived.sectors.map(sector => buildAnnularSectorCommands({
|
|
183
184
|
frame,
|
|
184
185
|
startTheta: sector.startTheta,
|
|
@@ -194,26 +195,15 @@ const rematerializeArcMark = action(
|
|
|
194
195
|
resolved.resolvedScales[colorScaleId].domain,
|
|
195
196
|
resolved.resolvedScales[colorScaleId].range
|
|
196
197
|
);
|
|
197
|
-
return resolved
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
})
|
|
207
|
-
.editGraphics({
|
|
208
|
-
target: id,
|
|
209
|
-
property: "strokeWidth",
|
|
210
|
-
value: config.stroke === false ? 0 : config.strokeWidth ?? 1
|
|
211
|
-
})
|
|
212
|
-
.editGraphics({
|
|
213
|
-
target: id,
|
|
214
|
-
property: "strokeDash",
|
|
215
|
-
value: commands.map(() => [])
|
|
216
|
-
});
|
|
198
|
+
return editMarkGraphic(resolved, id, {
|
|
199
|
+
length: commands.length,
|
|
200
|
+
commands,
|
|
201
|
+
fill: fills,
|
|
202
|
+
opacity: config.opacity ?? 1,
|
|
203
|
+
stroke: config.stroke === false ? "transparent" : config.stroke ?? "#ffffff",
|
|
204
|
+
strokeWidth: config.stroke === false ? 0 : config.strokeWidth ?? 1,
|
|
205
|
+
strokeDash: commands.map(() => [])
|
|
206
|
+
});
|
|
217
207
|
}
|
|
218
208
|
);
|
|
219
209
|
|
|
@@ -258,14 +248,19 @@ const editArcMark = action(
|
|
|
258
248
|
allowStrokeRemoval: true
|
|
259
249
|
})
|
|
260
250
|
);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
251
|
+
if (!canMaterializeArc(next, layer)) return next;
|
|
252
|
+
let rematerialized = next.rematerializeArcMark({ id: layer.id });
|
|
253
|
+
for (const step of getSourceDependentMarkSteps(rematerialized, layer.id)) {
|
|
254
|
+
rematerialized = rematerialized[step.op](step.args);
|
|
255
|
+
}
|
|
256
|
+
return rematerialized;
|
|
264
257
|
}
|
|
265
258
|
);
|
|
266
259
|
|
|
267
260
|
export function registerArcMarkActions(ProgramClass) {
|
|
268
|
-
ProgramClass.prototype
|
|
269
|
-
|
|
270
|
-
|
|
261
|
+
Object.assign(ProgramClass.prototype, {
|
|
262
|
+
createArcMark,
|
|
263
|
+
editArcMark,
|
|
264
|
+
rematerializeArcMark
|
|
265
|
+
});
|
|
271
266
|
}
|