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
|
@@ -21,9 +21,9 @@ function assertTitleScope(program, operation) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function hasRotation(graphic) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
return (graphic.items ?? [graphic]).some(
|
|
25
|
+
item => Object.hasOwn(item.properties, "rotation")
|
|
26
|
+
);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function ensureTextShape(program, id, component) {
|
|
@@ -45,14 +45,7 @@ function ensureTextShape(program, id, component) {
|
|
|
45
45
|
...placement
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
-
if (
|
|
49
|
-
return program.editGraphics({
|
|
50
|
-
target: id,
|
|
51
|
-
property: "length",
|
|
52
|
-
value: component.lines.length
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
if (collection && graphic.items.length !== component.lines.length) {
|
|
48
|
+
if (needsCollection && graphic.items?.length !== component.lines.length) {
|
|
56
49
|
return program.editGraphics({
|
|
57
50
|
target: id,
|
|
58
51
|
property: "length",
|
|
@@ -68,20 +61,20 @@ function distributed(value, count) {
|
|
|
68
61
|
|
|
69
62
|
function editTextGraphic(program, id, component, style) {
|
|
70
63
|
const count = component.lines.length;
|
|
71
|
-
let next = ensureTextShape(program, id, component)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
.editGraphics({ target: id, property
|
|
84
|
-
|
|
64
|
+
let next = ensureTextShape(program, id, component);
|
|
65
|
+
for (const [property, value] of Object.entries({
|
|
66
|
+
x: distributed(component.x, count),
|
|
67
|
+
y: distributed(component.y, count),
|
|
68
|
+
text: distributed(component.lines, count),
|
|
69
|
+
fill: style.color,
|
|
70
|
+
fontSize: style.fontSize,
|
|
71
|
+
fontFamily: style.fontFamily,
|
|
72
|
+
fontWeight: style.fontWeight,
|
|
73
|
+
textAlign: component.textAlign,
|
|
74
|
+
textBaseline: "middle"
|
|
75
|
+
})) {
|
|
76
|
+
next = next.editGraphics({ target: id, property, value });
|
|
77
|
+
}
|
|
85
78
|
if (component.explicitRotation) {
|
|
86
79
|
next = next.editGraphics({
|
|
87
80
|
target: id,
|
|
@@ -92,78 +85,39 @@ function editTextGraphic(program, id, component, style) {
|
|
|
92
85
|
return next;
|
|
93
86
|
}
|
|
94
87
|
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
88
|
+
function titleTextAction(kind, create) {
|
|
89
|
+
const suffix = `${kind[0].toUpperCase()}${kind.slice(1)}Text`;
|
|
90
|
+
const op = `${create ? "create" : "edit"}${suffix}`;
|
|
91
|
+
const id = `chart${kind[0].toUpperCase()}${kind.slice(1)}`;
|
|
92
|
+
return action({
|
|
93
|
+
op,
|
|
94
|
+
description: `${create ? "Create" : "Rematerialize"} chart ${kind} text.`
|
|
95
|
+
}, function (args = {}) {
|
|
96
|
+
noOptions(args, op);
|
|
97
|
+
const config = requireTitleConfig(this);
|
|
98
|
+
if (create && this.graphicSpec.objects[id] !== undefined) {
|
|
99
|
+
throw new Error(`${op} requires a missing chart ${kind} graphic.`);
|
|
100
|
+
}
|
|
101
|
+
const component = resolveTitleLayout(this, config)[kind];
|
|
102
|
+
if (component === undefined) {
|
|
103
|
+
throw new Error(`${op} requires semantic subtitle text.`);
|
|
104
|
+
}
|
|
105
|
+
if (!create) {
|
|
106
|
+
return editTextGraphic(this, id, component, config[`${kind}Style`]);
|
|
107
|
+
}
|
|
108
|
+
return this.createGraphics({
|
|
109
|
+
id,
|
|
110
|
+
type: "text",
|
|
111
|
+
...(component.lines.length > 1 ? { length: component.lines.length } : {}),
|
|
112
|
+
...resolveCanvasGraphicPlacement(this)
|
|
113
|
+
})[`edit${suffix}`]();
|
|
101
114
|
});
|
|
102
115
|
}
|
|
103
116
|
|
|
104
|
-
export const editTitleText =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const config = requireTitleConfig(this);
|
|
109
|
-
const layout = resolveTitleLayout(this, config);
|
|
110
|
-
return editTextGraphic(
|
|
111
|
-
this,
|
|
112
|
-
"chartTitle",
|
|
113
|
-
layout.title,
|
|
114
|
-
config.titleStyle
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
export const createTitleText = action(
|
|
120
|
-
{ op: "createTitleText", description: "Create chart title text." },
|
|
121
|
-
function (args = {}) {
|
|
122
|
-
noOptions(args, "createTitleText");
|
|
123
|
-
const config = requireTitleConfig(this);
|
|
124
|
-
if (this.graphicSpec.objects.chartTitle !== undefined) {
|
|
125
|
-
throw new Error("createTitleText requires a missing chart title graphic.");
|
|
126
|
-
}
|
|
127
|
-
const layout = resolveTitleLayout(this, config);
|
|
128
|
-
return createTextGraphic(this, "chartTitle", layout.title)
|
|
129
|
-
.editTitleText();
|
|
130
|
-
}
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
export const editSubtitleText = action(
|
|
134
|
-
{ op: "editSubtitleText", description: "Rematerialize chart subtitle text." },
|
|
135
|
-
function (args = {}) {
|
|
136
|
-
noOptions(args, "editSubtitleText");
|
|
137
|
-
const config = requireTitleConfig(this);
|
|
138
|
-
const layout = resolveTitleLayout(this, config);
|
|
139
|
-
if (layout.subtitle === undefined) {
|
|
140
|
-
throw new Error("editSubtitleText requires semantic subtitle text.");
|
|
141
|
-
}
|
|
142
|
-
return editTextGraphic(
|
|
143
|
-
this,
|
|
144
|
-
"chartSubtitle",
|
|
145
|
-
layout.subtitle,
|
|
146
|
-
config.subtitleStyle
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
export const createSubtitleText = action(
|
|
152
|
-
{ op: "createSubtitleText", description: "Create chart subtitle text." },
|
|
153
|
-
function (args = {}) {
|
|
154
|
-
noOptions(args, "createSubtitleText");
|
|
155
|
-
const config = requireTitleConfig(this);
|
|
156
|
-
if (this.graphicSpec.objects.chartSubtitle !== undefined) {
|
|
157
|
-
throw new Error("createSubtitleText requires a missing chart subtitle graphic.");
|
|
158
|
-
}
|
|
159
|
-
const layout = resolveTitleLayout(this, config);
|
|
160
|
-
if (layout.subtitle === undefined) {
|
|
161
|
-
throw new Error("createSubtitleText requires semantic subtitle text.");
|
|
162
|
-
}
|
|
163
|
-
return createTextGraphic(this, "chartSubtitle", layout.subtitle)
|
|
164
|
-
.editSubtitleText();
|
|
165
|
-
}
|
|
166
|
-
);
|
|
117
|
+
export const editTitleText = titleTextAction("title", false);
|
|
118
|
+
export const createTitleText = titleTextAction("title", true);
|
|
119
|
+
export const editSubtitleText = titleTextAction("subtitle", false);
|
|
120
|
+
export const createSubtitleText = titleTextAction("subtitle", true);
|
|
167
121
|
|
|
168
122
|
export const rematerializeTitle = action(
|
|
169
123
|
{ op: "rematerializeTitle", description: "Rematerialize chart title graphics." },
|
|
@@ -252,29 +252,20 @@ export function resolveTitleLayout(program, config) {
|
|
|
252
252
|
)
|
|
253
253
|
}, config);
|
|
254
254
|
const horizontal = ["top", "bottom"].includes(config.position);
|
|
255
|
-
let
|
|
256
|
-
let subtitle;
|
|
255
|
+
let component;
|
|
257
256
|
if (horizontal) {
|
|
258
257
|
const x = alignedTextAnchor(plot.x, plot.width, config.align);
|
|
259
258
|
const blockTop = config.position === "top"
|
|
260
259
|
? 16 + config.offset
|
|
261
260
|
: plot.y + plot.height + config.offset;
|
|
262
|
-
|
|
263
|
-
lines
|
|
261
|
+
component = (lines, centers) => ({
|
|
262
|
+
lines,
|
|
264
263
|
x,
|
|
265
|
-
y:
|
|
264
|
+
y: centers.map(value => blockTop + value),
|
|
266
265
|
textAlign: config.align,
|
|
267
266
|
rotation: 0,
|
|
268
|
-
explicitRotation: config.position !== "top" ||
|
|
269
|
-
};
|
|
270
|
-
subtitle = block.subtitleLines.length === 0 ? undefined : {
|
|
271
|
-
lines: block.subtitleLines,
|
|
272
|
-
x,
|
|
273
|
-
y: block.subtitleCenters.map(value => blockTop + value),
|
|
274
|
-
textAlign: config.align,
|
|
275
|
-
rotation: 0,
|
|
276
|
-
explicitRotation: config.position !== "top" || block.subtitleLines.length > 1
|
|
277
|
-
};
|
|
267
|
+
explicitRotation: config.position !== "top" || lines.length > 1
|
|
268
|
+
});
|
|
278
269
|
} else {
|
|
279
270
|
const rotation = config.position === "left" ? -Math.PI / 2 : Math.PI / 2;
|
|
280
271
|
const y = alignedTitleAnchor(plot.y, plot.height, block.width, config.align);
|
|
@@ -282,23 +273,19 @@ export function resolveTitleLayout(program, config) {
|
|
|
282
273
|
? 16 + config.offset
|
|
283
274
|
: canvas.properties.width - 16 + config.offset;
|
|
284
275
|
const mapX = value => config.position === "left" ? edge + value : edge - value;
|
|
285
|
-
|
|
286
|
-
lines
|
|
287
|
-
x:
|
|
288
|
-
y,
|
|
289
|
-
textAlign: "center",
|
|
290
|
-
rotation,
|
|
291
|
-
explicitRotation: true
|
|
292
|
-
};
|
|
293
|
-
subtitle = block.subtitleLines.length === 0 ? undefined : {
|
|
294
|
-
lines: block.subtitleLines,
|
|
295
|
-
x: block.subtitleCenters.map(mapX),
|
|
276
|
+
component = (lines, centers) => ({
|
|
277
|
+
lines,
|
|
278
|
+
x: centers.map(mapX),
|
|
296
279
|
y,
|
|
297
280
|
textAlign: "center",
|
|
298
281
|
rotation,
|
|
299
282
|
explicitRotation: true
|
|
300
|
-
};
|
|
283
|
+
});
|
|
301
284
|
}
|
|
285
|
+
const title = component(block.titleLines, block.titleCenters);
|
|
286
|
+
const subtitle = block.subtitleLines.length === 0
|
|
287
|
+
? undefined
|
|
288
|
+
: component(block.subtitleLines, block.subtitleCenters);
|
|
302
289
|
const titleBounds = unionTitleBounds([
|
|
303
290
|
resolveTitleComponentBounds(title, config.titleStyle),
|
|
304
291
|
...(subtitle === undefined
|
|
@@ -27,6 +27,8 @@ const AREA_OPTIONS = Object.freeze([
|
|
|
27
27
|
"fill", "opacity", "stroke", "strokeWidth", "curve"
|
|
28
28
|
]);
|
|
29
29
|
const CATEGORICAL_TYPES = Object.freeze(["nominal", "ordinal"]);
|
|
30
|
+
const OPERATION = "createViolinPlot";
|
|
31
|
+
const ROLE_ERROR = `${OPERATION} requires one categorical axis and one quantitative axis.`;
|
|
30
32
|
|
|
31
33
|
function inferFieldType(dataset, encoding, label) {
|
|
32
34
|
if (encoding.fieldType !== undefined) return encoding.fieldType;
|
|
@@ -51,29 +53,6 @@ function normalizePosition(dataset, value, label) {
|
|
|
51
53
|
};
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
function normalizeDensity(value) {
|
|
55
|
-
if (value === undefined) return { options: {}, width: undefined };
|
|
56
|
-
validateOptionObject(value, DENSITY_OPTIONS, "createViolinPlot density");
|
|
57
|
-
if (value.width !== undefined) {
|
|
58
|
-
validateOptionObject(
|
|
59
|
-
value.width,
|
|
60
|
-
WIDTH_OPTIONS,
|
|
61
|
-
"createViolinPlot density.width"
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
const { width, ...options } = value;
|
|
65
|
-
return { options, width };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function normalizeSplit(value) {
|
|
69
|
-
if (value === undefined) return undefined;
|
|
70
|
-
validateOptionObject(value, SPLIT_OPTIONS, "createViolinPlot split");
|
|
71
|
-
if (typeof value.field !== "string" || value.field.length === 0) {
|
|
72
|
-
throw new TypeError("createViolinPlot split.field must be a non-empty string.");
|
|
73
|
-
}
|
|
74
|
-
return { ...value };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
56
|
function axisGuideOptions(value, field) {
|
|
78
57
|
if (value === false) return false;
|
|
79
58
|
return {
|
|
@@ -109,55 +88,69 @@ export const createViolinPlot = action(
|
|
|
109
88
|
description: "Create a categorical kernel-density violin plot."
|
|
110
89
|
},
|
|
111
90
|
function (args = {}) {
|
|
112
|
-
validateFacadeOptions(args, OPTIONS,
|
|
91
|
+
validateFacadeOptions(args, OPTIONS, OPERATION);
|
|
113
92
|
const id = resolveFacadeId(this, args.id, {
|
|
114
93
|
defaultId: "violinPlot",
|
|
115
|
-
operation:
|
|
94
|
+
operation: OPERATION
|
|
116
95
|
});
|
|
117
|
-
const data = resolveFacadeData(this, args.data,
|
|
96
|
+
const data = resolveFacadeData(this, args.data, OPERATION);
|
|
118
97
|
const dataset = findDataset(this, data);
|
|
119
|
-
const x = normalizePosition(dataset, args.x,
|
|
120
|
-
const y = normalizePosition(dataset, args.y,
|
|
98
|
+
const x = normalizePosition(dataset, args.x, `${OPERATION} x`);
|
|
99
|
+
const y = normalizePosition(dataset, args.y, `${OPERATION} y`);
|
|
121
100
|
const xCategorical = CATEGORICAL_TYPES.includes(x.fieldType);
|
|
122
101
|
const yCategorical = CATEGORICAL_TYPES.includes(y.fieldType);
|
|
123
102
|
if (xCategorical === yCategorical || (
|
|
124
103
|
x.fieldType !== "quantitative" && y.fieldType !== "quantitative"
|
|
125
104
|
)) {
|
|
126
|
-
throw new Error(
|
|
127
|
-
"createViolinPlot requires one categorical axis and one quantitative axis."
|
|
128
|
-
);
|
|
105
|
+
throw new Error(ROLE_ERROR);
|
|
129
106
|
}
|
|
130
107
|
const category = xCategorical ? x : y;
|
|
131
108
|
const value = xCategorical ? y : x;
|
|
132
109
|
if (value.fieldType !== "quantitative") {
|
|
133
|
-
throw new Error(
|
|
134
|
-
|
|
135
|
-
|
|
110
|
+
throw new Error(ROLE_ERROR);
|
|
111
|
+
}
|
|
112
|
+
let split = args.split;
|
|
113
|
+
if (split !== undefined) {
|
|
114
|
+
validateOptionObject(split, SPLIT_OPTIONS, `${OPERATION} split`);
|
|
115
|
+
if (typeof split.field !== "string" || split.field.length === 0) {
|
|
116
|
+
throw new TypeError(`${OPERATION} split.field must be a non-empty string.`);
|
|
117
|
+
}
|
|
118
|
+
split = { ...split };
|
|
136
119
|
}
|
|
137
|
-
const split = normalizeSplit(args.split);
|
|
138
120
|
if (split?.field === category.field) {
|
|
139
|
-
throw new Error(
|
|
121
|
+
throw new Error(`${OPERATION} split field must differ from its category field.`);
|
|
140
122
|
}
|
|
141
|
-
const color = normalizeEncoding(args.color,
|
|
123
|
+
const color = normalizeEncoding(args.color, `${OPERATION} color`);
|
|
142
124
|
if (
|
|
143
125
|
color !== undefined &&
|
|
144
126
|
![category.field, split?.field].includes(color.field)
|
|
145
127
|
) {
|
|
146
128
|
throw new Error(
|
|
147
|
-
|
|
129
|
+
`${OPERATION} color must encode its category or split field.`
|
|
148
130
|
);
|
|
149
131
|
}
|
|
150
|
-
const density =
|
|
132
|
+
const density = args.density === undefined ? {} : args.density;
|
|
133
|
+
if (args.density !== undefined) {
|
|
134
|
+
validateOptionObject(density, DENSITY_OPTIONS, `${OPERATION} density`);
|
|
135
|
+
if (density.width !== undefined) {
|
|
136
|
+
validateOptionObject(
|
|
137
|
+
density.width,
|
|
138
|
+
WIDTH_OPTIONS,
|
|
139
|
+
`${OPERATION} density.width`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const { width: densityWidth, ...densityOptions } = density;
|
|
151
144
|
const area = normalizeAppearance(
|
|
152
145
|
args.area,
|
|
153
146
|
AREA_OPTIONS,
|
|
154
|
-
|
|
147
|
+
`${OPERATION} area`
|
|
155
148
|
);
|
|
156
149
|
if (color !== undefined && Object.hasOwn(area, "fill")) {
|
|
157
|
-
throw new Error(
|
|
150
|
+
throw new Error(`${OPERATION} area.fill cannot be combined with color.`);
|
|
158
151
|
}
|
|
159
152
|
const guides = guideOptions(
|
|
160
|
-
normalizeGuides(args.guides,
|
|
153
|
+
normalizeGuides(args.guides, OPERATION),
|
|
161
154
|
x,
|
|
162
155
|
y,
|
|
163
156
|
category,
|
|
@@ -184,10 +177,10 @@ export const createViolinPlot = action(
|
|
|
184
177
|
densityChannel: xCategorical ? "x" : "y",
|
|
185
178
|
...(args.coordinate === undefined ? {} : { coordinate: args.coordinate }),
|
|
186
179
|
...(value.scale === undefined ? {} : { valueScale: value.scale }),
|
|
187
|
-
...
|
|
180
|
+
...densityOptions,
|
|
188
181
|
placement: {
|
|
189
182
|
type: "category",
|
|
190
|
-
...(
|
|
183
|
+
...(densityWidth === undefined ? {} : { width: densityWidth }),
|
|
191
184
|
...(split === undefined ? {} : { split }),
|
|
192
185
|
...(category.scale === undefined ? {} : { scale: category.scale })
|
|
193
186
|
}
|
package/src/core/action.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { cloneAndFreeze, freezeOwned, isPlainObject } from "./immutable.js";
|
|
2
2
|
|
|
3
|
+
const metadataByWrappedAction = new WeakMap();
|
|
4
|
+
|
|
5
|
+
export function getWrappedActionMetadata(value) {
|
|
6
|
+
return metadataByWrappedAction.get(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
function summarizeObject(value, ancestors = new WeakSet()) {
|
|
4
10
|
if (ancestors.has(value)) {
|
|
5
11
|
throw new TypeError("Action arguments must not contain circular references.");
|
|
@@ -107,24 +113,33 @@ export function action(metadata, implementation) {
|
|
|
107
113
|
throw new Error(`Unknown action scope "${scope}".`);
|
|
108
114
|
}
|
|
109
115
|
|
|
110
|
-
|
|
116
|
+
const ownedMetadata = freezeOwned({
|
|
117
|
+
op: metadata.op,
|
|
118
|
+
description: metadata.description,
|
|
119
|
+
scope
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const wrappedAction = function wrappedAction(args = {}) {
|
|
111
123
|
if (!isPlainObject(args)) {
|
|
112
124
|
throw new TypeError("Action arguments must be a plain object.");
|
|
113
125
|
}
|
|
114
126
|
|
|
115
|
-
if (scope === "unit") this._assertUnitProgram(
|
|
116
|
-
if (scope === "composition") this._assertCompositionProgram(
|
|
127
|
+
if (scope === "unit") this._assertUnitProgram(ownedMetadata.op);
|
|
128
|
+
if (scope === "composition") this._assertCompositionProgram(ownedMetadata.op);
|
|
117
129
|
|
|
118
130
|
const entered = this._enterAction({
|
|
119
|
-
...
|
|
131
|
+
...ownedMetadata,
|
|
120
132
|
args: summarizeArgs(args)
|
|
121
133
|
});
|
|
122
134
|
const result = implementation.call(entered, args);
|
|
123
135
|
|
|
124
136
|
if (!(result instanceof this.constructor)) {
|
|
125
|
-
throw new TypeError(`${
|
|
137
|
+
throw new TypeError(`${ownedMetadata.op} must return a ChartProgram.`);
|
|
126
138
|
}
|
|
127
139
|
|
|
128
140
|
return result._exitAction();
|
|
129
141
|
};
|
|
142
|
+
|
|
143
|
+
metadataByWrappedAction.set(wrappedAction, ownedMetadata);
|
|
144
|
+
return wrappedAction;
|
|
130
145
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { getWrappedActionMetadata } from "./action.js";
|
|
2
|
+
import { isPlainObject } from "./immutable.js";
|
|
3
|
+
|
|
4
|
+
const ACTION_NAME_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
5
|
+
const DEFINITION_KEYS = new Set(["name", "actions"]);
|
|
6
|
+
const registeredExtensionNames = new Set();
|
|
7
|
+
|
|
8
|
+
function readDataProperty(object, key, owner) {
|
|
9
|
+
const descriptor = Object.getOwnPropertyDescriptor(object, key);
|
|
10
|
+
if (descriptor === undefined) {
|
|
11
|
+
throw new TypeError(`${owner} requires a ${key} property.`);
|
|
12
|
+
}
|
|
13
|
+
if (!("value" in descriptor)) {
|
|
14
|
+
throw new TypeError(`${owner} ${key} must be a data property.`);
|
|
15
|
+
}
|
|
16
|
+
return descriptor.value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function validateDefinitionShape(definition) {
|
|
20
|
+
if (!isPlainObject(definition)) {
|
|
21
|
+
throw new TypeError("Extension definition must be a plain object.");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const key of Reflect.ownKeys(definition)) {
|
|
25
|
+
if (typeof key !== "string" || !DEFINITION_KEYS.has(key)) {
|
|
26
|
+
throw new TypeError(`Unknown extension definition property "${String(key)}".`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const name = readDataProperty(definition, "name", "Extension definition");
|
|
31
|
+
const actions = readDataProperty(
|
|
32
|
+
definition,
|
|
33
|
+
"actions",
|
|
34
|
+
"Extension definition"
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (
|
|
38
|
+
typeof name !== "string" ||
|
|
39
|
+
name.length === 0 ||
|
|
40
|
+
name !== name.trim()
|
|
41
|
+
) {
|
|
42
|
+
throw new TypeError("Extension name must be a non-empty trimmed string.");
|
|
43
|
+
}
|
|
44
|
+
if (!isPlainObject(actions)) {
|
|
45
|
+
throw new TypeError("Extension actions must be a plain object.");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { name, actions };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function registerProgramExtension(ProgramClass, definition) {
|
|
52
|
+
const { name, actions } = validateDefinitionShape(definition);
|
|
53
|
+
|
|
54
|
+
if (registeredExtensionNames.has(name)) {
|
|
55
|
+
throw new Error(`Extension "${name}" is already registered.`);
|
|
56
|
+
}
|
|
57
|
+
if (!Object.isExtensible(ProgramClass.prototype)) {
|
|
58
|
+
throw new Error("ChartProgram prototype does not accept extension actions.");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const program = new ProgramClass();
|
|
62
|
+
const descriptors = Object.create(null);
|
|
63
|
+
const actionNames = Reflect.ownKeys(actions);
|
|
64
|
+
if (actionNames.length === 0) {
|
|
65
|
+
throw new TypeError("Extension actions must contain at least one action.");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const actionName of actionNames) {
|
|
69
|
+
if (
|
|
70
|
+
typeof actionName !== "string" ||
|
|
71
|
+
!ACTION_NAME_PATTERN.test(actionName)
|
|
72
|
+
) {
|
|
73
|
+
throw new TypeError(
|
|
74
|
+
`Extension action name "${String(actionName)}" must be a JavaScript identifier.`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const action = readDataProperty(actions, actionName, "Extension actions");
|
|
79
|
+
const metadata = getWrappedActionMetadata(action);
|
|
80
|
+
if (metadata === undefined) {
|
|
81
|
+
throw new TypeError(
|
|
82
|
+
`Extension action "${actionName}" must be created with action().`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (metadata.op !== actionName) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Extension action "${actionName}" must use the same action op.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (actionName in program) {
|
|
91
|
+
throw new Error(`ChartProgram action "${actionName}" is already defined.`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
descriptors[actionName] = {
|
|
95
|
+
value: action,
|
|
96
|
+
configurable: false,
|
|
97
|
+
enumerable: true,
|
|
98
|
+
writable: false
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Object.defineProperties(ProgramClass.prototype, descriptors);
|
|
103
|
+
registeredExtensionNames.add(name);
|
|
104
|
+
}
|