ggaction 0.0.16 → 0.0.18
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 +66 -0
- package/README.md +3 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +10 -4
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +3 -2
- package/src/actions/boxPlots/create.js +14 -4
- package/src/actions/boxPlots/edit.js +39 -33
- package/src/actions/boxPlots/materialize.js +7 -3
- package/src/actions/boxPlots/options.js +37 -11
- package/src/actions/canvas/actions.js +70 -95
- package/src/actions/canvas/fitting.js +16 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +28 -0
- package/src/actions/charts/area.js +2 -2
- package/src/actions/charts/bar.js +34 -7
- package/src/actions/charts/ecdf.js +4 -18
- package/src/actions/charts/endpoints.js +4 -18
- package/src/actions/charts/heatmap.js +4 -4
- package/src/actions/charts/index.js +2 -0
- package/src/actions/charts/interval-regression.js +2 -2
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/pie.js +3 -47
- package/src/actions/charts/radial.js +34 -12
- package/src/actions/charts/scatter.js +16 -2
- package/src/actions/charts/shared.js +2 -2
- package/src/actions/charts/text.js +35 -0
- package/src/actions/composition/actions.js +6 -2
- package/src/actions/data/bin.js +1 -1
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/create.js +18 -5
- package/src/actions/data/density.js +1 -1
- package/src/actions/data/derived.js +15 -1
- package/src/actions/data/edit.js +20 -7
- package/src/actions/data/filter.js +15 -4
- package/src/actions/data/index.js +3 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/regression.js +6 -2
- package/src/actions/data/revise.js +37 -5
- package/src/actions/data/shared.js +33 -4
- package/src/actions/data/sort.js +21 -0
- package/src/actions/data/stack.js +3 -1
- package/src/actions/data/summary.js +1 -1
- package/src/actions/encodings/angle.js +4 -1
- package/src/actions/encodings/appearance.js +43 -31
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +5 -2
- package/src/actions/encodings/color/index.js +5 -2
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/offset.js +4 -1
- package/src/actions/encodings/position/policies/arc.js +12 -10
- package/src/actions/encodings/position/policies/area.js +3 -3
- package/src/actions/encodings/position/policies/bar.js +15 -6
- package/src/actions/encodings/position/policies/line.js +17 -6
- package/src/actions/encodings/position/resolve.js +9 -3
- package/src/actions/encodings/ranged.js +23 -1
- package/src/actions/encodings/remove.js +3 -2
- package/src/actions/encodings/ruleAppearance.js +4 -1
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/stroke.js +4 -1
- package/src/actions/encodings/strokeDash.js +6 -1
- package/src/actions/encodings/text.js +12 -4
- package/src/actions/errorBands/create.js +2 -2
- package/src/actions/errorBands/edit.js +2 -1
- package/src/actions/facets/actions.js +158 -240
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axis.js +16 -4
- package/src/actions/guides/axes/edit.js +4 -4
- package/src/actions/guides/axes/labels.js +120 -46
- package/src/actions/guides/axes/lines.js +6 -5
- package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
- package/src/actions/guides/axes/parallel/policy.js +8 -7
- package/src/actions/guides/axes/remove.js +3 -2
- package/src/actions/guides/axes/tickGroups.js +5 -2
- package/src/actions/guides/axes/ticks.js +6 -3
- package/src/actions/guides/axes/titles.js +71 -26
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeLegend.js +10 -2
- package/src/actions/guides/grids/grid.js +7 -1
- package/src/actions/guides/grids/resolve.js +59 -12
- package/src/actions/guides/legends/blocks.js +3 -1
- package/src/actions/guides/legends/categorical/actions.js +70 -17
- package/src/actions/guides/legends/categorical/components.js +12 -8
- package/src/actions/guides/legends/categorical/index.js +6 -0
- package/src/actions/guides/legends/categorical/layout.js +37 -8
- package/src/actions/guides/legends/categorical/options.js +31 -20
- package/src/actions/guides/legends/categorical/resolve.js +2 -2
- package/src/actions/guides/legends/categorical/symbols.js +18 -12
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
- package/src/actions/guides/legends/continuous/common.js +1 -447
- package/src/actions/guides/legends/continuous/gradient.js +47 -34
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +19 -12
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/remove.js +3 -0
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +51 -43
- package/src/actions/guides/legends/strokeWidth.js +11 -29
- package/src/actions/guides/legends/target.js +3 -3
- package/src/actions/guides/legends/transition.js +21 -7
- package/src/actions/guides/polar/axes/labels.js +4 -2
- package/src/actions/guides/polar/axes/titles.js +7 -5
- package/src/actions/guides/polar/resolve.js +6 -14
- package/src/actions/index.js +16 -16
- package/src/actions/marks/arc/actions.js +3 -10
- package/src/actions/marks/area/actions.js +8 -5
- package/src/actions/marks/area/materialize.js +8 -9
- package/src/actions/marks/bar/create.js +17 -4
- package/src/actions/marks/bar/edit.js +18 -7
- package/src/actions/marks/bar/materialize.js +9 -6
- package/src/actions/marks/line/actions.js +11 -4
- package/src/actions/marks/line/materialize.js +9 -5
- package/src/actions/marks/point/create.js +8 -1
- package/src/actions/marks/point/edit.js +13 -6
- package/src/actions/marks/point/materialize.js +4 -2
- package/src/actions/marks/rect/actions.js +26 -11
- package/src/actions/marks/remove.js +2 -0
- package/src/actions/marks/rule/actions.js +31 -12
- package/src/actions/marks/text/actions.js +42 -11
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/marks/tick/actions.js +23 -8
- package/src/actions/primitives/semanticAction.js +37 -3
- package/src/actions/primitives/semanticValidation/dataset.js +13 -1
- package/src/actions/primitives/semanticValidation/layer.js +26 -2
- package/src/actions/primitives/semanticValidation/scale.js +6 -0
- package/src/actions/regression/create.js +26 -5
- package/src/actions/regression/edit.js +50 -15
- package/src/actions/regression/follow.js +22 -0
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +20 -15
- package/src/actions/scales/consumers/common.js +2 -1
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +10 -4
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/editPolicy.js +9 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/parallel.js +1 -1
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +49 -2
- package/src/actions/scales/quantitativeColor.js +3 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +83 -27
- package/src/actions/theme/state.js +12 -0
- package/src/actions/titles/actions.js +5 -18
- package/src/actions/titles/resolve.js +41 -19
- package/src/actions/violinPlots/edit.js +3 -20
- package/src/core/action.js +3 -2
- package/src/core/compositionState.js +4 -1
- package/src/core/font.js +9 -2
- package/src/core/optionRequirements.js +1 -0
- package/src/core/programState.js +5 -1
- package/src/core/resourceReferences.js +3 -0
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +26 -0
- package/src/core/vocabulary.js +4 -0
- package/src/grammar/aggregate.js +4 -18
- package/src/grammar/arcs.js +1 -386
- package/src/grammar/areaPaint.js +10 -0
- package/src/grammar/areaSeries.js +18 -10
- package/src/grammar/bars/geometry.js +30 -3
- package/src/grammar/bars/policy.js +12 -1
- package/src/grammar/bin.js +41 -22
- package/src/grammar/bin2d.js +1 -6
- package/src/grammar/boxPlot.js +56 -9
- package/src/grammar/categoryOrder.js +1 -6
- package/src/grammar/complete.js +15 -19
- package/src/grammar/computed.js +92 -25
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +382 -0
- package/src/grammar/density.js +53 -5
- package/src/grammar/displayLabels.js +28 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +5 -0
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/filter.js +133 -39
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +34 -10
- package/src/grammar/itemMissing.js +59 -0
- package/src/grammar/legendGradient.js +18 -0
- package/src/grammar/legendGroups.js +1 -0
- package/src/grammar/legendTitleStyle.js +22 -0
- package/src/grammar/lineSeries.js +32 -17
- package/src/grammar/markLabels.js +5 -2
- package/src/grammar/normalize.js +4 -54
- package/src/grammar/numeric.js +1 -290
- package/src/grammar/parallelCoordinates.js +1 -6
- package/src/grammar/parseTime.js +41 -0
- package/src/grammar/pathCommands.js +11 -4
- package/src/grammar/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +63 -16
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/models.js +116 -59
- package/src/grammar/regression/parameters.js +69 -6
- package/src/grammar/roundedRect.js +42 -54
- package/src/grammar/scales/appearance.js +1 -206
- package/src/grammar/scales/color.js +1 -382
- package/src/grammar/scales/colorConsumers.js +5 -2
- package/src/grammar/scales/definition.js +1 -190
- package/src/grammar/scales/discretized.js +1 -195
- package/src/grammar/scales/size.js +1 -456
- package/src/grammar/scales/temporal.js +2 -13
- package/src/grammar/scales/transformed.js +1 -383
- package/src/grammar/scales/types.js +1 -1
- package/src/grammar/scales/validation.js +1 -66
- package/src/grammar/schemas/concreteGraphic.js +11 -0
- package/src/grammar/schemas/graphic.js +3 -0
- package/src/grammar/schemas/semanticPath.js +5 -3
- package/src/grammar/sort.js +88 -0
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +120 -45
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +16 -7
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transformTopology.js +1 -0
- package/src/grammar/transforms.js +25 -12
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspection.js +495 -0
- package/src/inspectionDescriptors.js +3 -0
- package/src/layout/canvas.js +1 -110
- package/src/layout/facets.js +1 -244
- package/src/layout/labels.js +1 -0
- package/src/layout/legend.js +13 -0
- package/src/layout/legendGroup.js +1 -0
- package/src/layout/legendItems.js +59 -15
- package/src/layout/legendLane.js +1 -441
- package/src/layout/legendText.js +1 -0
- package/src/layout/title.js +35 -13
- package/src/materialization/bars/aggregate.js +11 -5
- package/src/materialization/bars/histogram.js +4 -1
- package/src/materialization/bars/ranged.js +19 -6
- package/src/materialization/bars/resolve.js +26 -3
- package/src/materialization/coordinateBounds.js +8 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/placement.js +19 -8
- package/src/materialization/facetGuides/preparation.js +15 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +1 -1
- package/src/materialization/guides/layout.js +1 -57
- package/src/materialization/guides/resources.js +1 -123
- package/src/materialization/layout.js +53 -0
- package/src/materialization/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/revisionIdentity.js +19 -0
- package/src/materialization/scaleGuideDependencies.js +1 -1
- package/src/materialization/scales/map.js +3 -0
- package/src/materialization/scales/policies/arc.js +8 -7
- package/src/materialization/scales/policies/bar.js +19 -7
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +72 -7
- package/src/materialization/selection/items/arc.js +1 -1
- package/src/materialization/selection/items/bar.js +7 -6
- package/src/materialization/selection/items/path.js +7 -6
- package/src/materialization/selection/items/rect.js +2 -2
- package/src/materialization/text.js +17 -6
- package/src/materialization/textLines.js +15 -0
- package/src/persistence/codec.js +37 -6
- package/src/persistence/validation.js +47 -0
- package/src/persistence.js +94 -8
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +2 -1
- package/src/renderers/canvas/line.js +1 -0
- package/src/renderers/canvas/path.js +1 -1
- package/src/renderers/canvas/rect.js +11 -8
- package/src/renderers/canvas/stroke.js +2 -1
- package/src/renderers/canvas/text.js +5 -3
- package/src/renderers/svg.js +4 -1
- package/src/selectors/layers.js +19 -0
- package/src/theme/defaults.js +8 -2
- package/src/version.js +1 -1
- package/types/basic.d.ts +1 -0
- package/types/index.d.ts +7 -0
- package/types/inspection.d.ts +17 -0
- package/types/program.d.ts +494 -112
package/src/persistence.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { ChartProgram } from "./ChartProgram.js";
|
|
2
2
|
import { BasicChartProgram } from "./BasicChartProgram.js";
|
|
3
|
-
import { cloneAndFreeze } from "./core/immutable.js";
|
|
3
|
+
import { cloneAndFreeze, freezeOwned } from "./core/immutable.js";
|
|
4
4
|
import { hasRegisteredExtension, isBuiltinProgramClass } from "./core/extensionRegistry.js";
|
|
5
5
|
import { packageVersion } from "./version.js";
|
|
6
|
-
import { decodeValue, encodeValue, invalidSnapshot } from "./persistence/codec.js";
|
|
6
|
+
import { decodeValue, encodeValue, encodeSharedValue, invalidSnapshot } from "./persistence/codec.js";
|
|
7
7
|
import { exactKeys, requireObject, STATE_KEYS, validateGraphic, validateProgramState, validateTrace } from "./persistence/validation.js";
|
|
8
|
+
import {
|
|
9
|
+
deriveTransformSchema,
|
|
10
|
+
inferDatasetSchema,
|
|
11
|
+
validateDatasetSchema
|
|
12
|
+
} from "./grammar/datasetSchema.js";
|
|
13
|
+
import { restoreResolvedScaleBindings } from "./actions/scales/preview.js";
|
|
8
14
|
|
|
9
15
|
function canonicalState(program, extensions, path = "payload") {
|
|
10
16
|
if (!(program instanceof ChartProgram || program instanceof BasicChartProgram) || !isBuiltinProgramClass(program.constructor)) {
|
|
@@ -19,7 +25,7 @@ function canonicalState(program, extensions, path = "payload") {
|
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
function envelope(kind, payload, extensions = []) {
|
|
22
|
-
return JSON.stringify({ schemaVersion: 1, kind, packageVersion, extensions, payload: encodeValue(payload) });
|
|
28
|
+
return JSON.stringify({ schemaVersion: kind === "editable" ? 3 : 1, kind, packageVersion, extensions, payload: kind === "editable" ? encodeSharedValue(payload) : encodeValue(payload) });
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
function readEnvelope(text, kind) {
|
|
@@ -27,24 +33,87 @@ function readEnvelope(text, kind) {
|
|
|
27
33
|
let value;
|
|
28
34
|
try { value = JSON.parse(text); } catch { throw invalidSnapshot("Snapshot must be valid JSON.", "snapshot"); }
|
|
29
35
|
exactKeys(value, ["schemaVersion", "kind", "packageVersion", "extensions", "payload"], "snapshot");
|
|
30
|
-
|
|
36
|
+
const versions = kind === "editable" ? [1, 2, 3] : [1];
|
|
37
|
+
if (!versions.includes(value.schemaVersion) || value.kind !== kind) {
|
|
38
|
+
throw invalidSnapshot(
|
|
39
|
+
`Unsupported snapshot schema or kind; expected schema ${versions.join(" or ")} ${kind}.`,
|
|
40
|
+
"snapshot"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
31
43
|
if (typeof value.packageVersion !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(value.packageVersion)) throw invalidSnapshot("Invalid snapshot packageVersion.", "snapshot.packageVersion");
|
|
32
44
|
if (!Array.isArray(value.extensions) || new Set(value.extensions).size !== value.extensions.length ||
|
|
33
45
|
value.extensions.some(name => typeof name !== "string" || !hasRegisteredExtension(name)) || (kind === "graphic" && value.extensions.length)) {
|
|
34
46
|
throw invalidSnapshot("Snapshot requires unknown, duplicate, or inapplicable extensions.", "snapshot.extensions");
|
|
35
47
|
}
|
|
48
|
+
if (value.schemaVersion < 3 && value.payload?.[0] === "shared") {
|
|
49
|
+
throw invalidSnapshot("Shared array payloads require editable snapshot version 3.", "snapshot.payload");
|
|
50
|
+
}
|
|
36
51
|
return { ...value, payload: decodeValue(value.payload) };
|
|
37
52
|
}
|
|
38
53
|
|
|
39
|
-
function restoreState(state, extensions, path = "payload") {
|
|
54
|
+
function restoreState(state, extensions, path = "payload", { requireSchemas = false } = {}) {
|
|
40
55
|
exactKeys(state, STATE_KEYS, path);
|
|
41
56
|
if (!Array.isArray(state.actionStack) || state.actionStack.length) throw invalidSnapshot("Snapshot actionStack must be empty.", `${path}.actionStack`);
|
|
42
57
|
requireObject(state.children, `${path}.children`);
|
|
43
58
|
validateTrace(state.trace, ChartProgram, extensions, `${path}.trace`);
|
|
44
|
-
const children = Object.fromEntries(Object.entries(state.children).map(([id, child]) => [id, restoreState(
|
|
59
|
+
const children = Object.fromEntries(Object.entries(state.children).map(([id, child]) => [id, restoreState(
|
|
60
|
+
child,
|
|
61
|
+
extensions,
|
|
62
|
+
`${path}.children.${id}`,
|
|
63
|
+
{ requireSchemas }
|
|
64
|
+
)]));
|
|
45
65
|
const program = new ChartProgram({ ...state, children });
|
|
46
66
|
validateProgramState(program, path);
|
|
47
|
-
|
|
67
|
+
if (requireSchemas) {
|
|
68
|
+
for (const dataset of program.semanticSpec.datasets) {
|
|
69
|
+
if (dataset.schema === undefined) {
|
|
70
|
+
throw invalidSnapshot(`Dataset "${dataset.id}" requires schema in editable snapshot version 2 or later.`, `${path}.semanticSpec.datasets`);
|
|
71
|
+
}
|
|
72
|
+
validateDatasetSchema(dataset.schema);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return restoreResolvedScaleBindings(program);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function migrateStateSchemas(state) {
|
|
79
|
+
const datasets = state.semanticSpec.datasets;
|
|
80
|
+
const byId = new Map(datasets.map(dataset => [dataset.id, dataset]));
|
|
81
|
+
const schemas = new Map();
|
|
82
|
+
const active = new Set();
|
|
83
|
+
function schemaFor(id) {
|
|
84
|
+
if (schemas.has(id)) return schemas.get(id);
|
|
85
|
+
if (active.has(id)) throw invalidSnapshot(`Cyclic data source "${id}".`, "payload.semanticSpec.datasets");
|
|
86
|
+
active.add(id);
|
|
87
|
+
const dataset = byId.get(id);
|
|
88
|
+
let schema;
|
|
89
|
+
if (dataset.schema !== undefined) {
|
|
90
|
+
schema = validateDatasetSchema(dataset.schema);
|
|
91
|
+
} else if (dataset.source === undefined) {
|
|
92
|
+
schema = inferDatasetSchema(dataset.values ?? []);
|
|
93
|
+
} else {
|
|
94
|
+
const sourceSchema = schemaFor(dataset.source);
|
|
95
|
+
schema = deriveTransformSchema(
|
|
96
|
+
sourceSchema,
|
|
97
|
+
dataset.transform?.[0] ?? {},
|
|
98
|
+
dataset.values,
|
|
99
|
+
{ sourceId: dataset.source, ownerId: dataset.id }
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
active.delete(id);
|
|
103
|
+
schemas.set(id, schema);
|
|
104
|
+
return schema;
|
|
105
|
+
}
|
|
106
|
+
const semanticSpec = {
|
|
107
|
+
...state.semanticSpec,
|
|
108
|
+
datasets: datasets.map(dataset => ({ ...dataset, schema: schemaFor(dataset.id) }))
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
...state,
|
|
112
|
+
semanticSpec,
|
|
113
|
+
children: Object.fromEntries(Object.entries(state.children).map(
|
|
114
|
+
([id, child]) => [id, migrateStateSchemas(child)]
|
|
115
|
+
))
|
|
116
|
+
};
|
|
48
117
|
}
|
|
49
118
|
|
|
50
119
|
export function serializeProgram(program) {
|
|
@@ -52,10 +121,27 @@ export function serializeProgram(program) {
|
|
|
52
121
|
return envelope("editable", canonicalState(program, extensions), [...extensions].sort());
|
|
53
122
|
}
|
|
54
123
|
|
|
124
|
+
// Decoding creates fresh plain containers. Own them once across the whole graph
|
|
125
|
+
// so child constructors retain shared immutable data instead of copying each pool.
|
|
126
|
+
function ownDecodedState(value, seen = new WeakSet()) {
|
|
127
|
+
if (value === null || typeof value !== "object" || seen.has(value)) return;
|
|
128
|
+
seen.add(value);
|
|
129
|
+
for (const child of Object.values(value)) ownDecodedState(child, seen);
|
|
130
|
+
freezeOwned(value);
|
|
131
|
+
}
|
|
132
|
+
|
|
55
133
|
export function deserializeProgram(text) {
|
|
56
134
|
const stored = readEnvelope(text, "editable");
|
|
57
135
|
const extensions = new Set();
|
|
58
|
-
|
|
136
|
+
let payload = stored.payload;
|
|
137
|
+
if (stored.schemaVersion === 1) {
|
|
138
|
+
// Validate the historical state before adding any inferred metadata. Migration
|
|
139
|
+
// must never repair an invalid reference graph or malformed payload.
|
|
140
|
+
restoreState(payload, new Set());
|
|
141
|
+
payload = migrateStateSchemas(payload);
|
|
142
|
+
}
|
|
143
|
+
ownDecodedState(payload);
|
|
144
|
+
const program = restoreState(payload, extensions, "payload", { requireSchemas: true });
|
|
59
145
|
if (extensions.size !== stored.extensions.length || stored.extensions.some(name => !extensions.has(name))) {
|
|
60
146
|
throw invalidSnapshot("Snapshot extensions must exactly match its action traces.", "snapshot.extensions");
|
|
61
147
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveCanvasSolidPaint } from "./fill.js";
|
|
1
2
|
import { requireFiniteProperty } from "./validation.js";
|
|
2
3
|
import { validateConcreteGraphicProperties } from
|
|
3
4
|
"../../grammar/schemas/concreteGraphic.js";
|
|
@@ -25,7 +26,7 @@ function drawCircle(context, child, collectionId) {
|
|
|
25
26
|
throw new Error(`Graphic "${graphicId}" requires opacity from 0 to 1.`);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
context.fillStyle = properties.fill;
|
|
29
|
+
context.fillStyle = resolveCanvasSolidPaint(properties.fill);
|
|
29
30
|
context.globalAlpha = opacity;
|
|
30
31
|
context.beginPath();
|
|
31
32
|
context.arc(x, y, radius, 0, Math.PI * 2);
|
|
@@ -44,8 +45,10 @@ function drawCircle(context, child, collectionId) {
|
|
|
44
45
|
`Graphic "${graphicId}" requires a non-negative strokeWidth.`
|
|
45
46
|
);
|
|
46
47
|
}
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
if (strokeWidth > 0) {
|
|
49
|
+
applyCanvasStroke(context, properties);
|
|
50
|
+
context.stroke();
|
|
51
|
+
}
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
|
|
@@ -4,10 +4,16 @@ import {
|
|
|
4
4
|
} from "../../grammar/paint.js";
|
|
5
5
|
import { resolveCanvasGradientCoordinates } from "./native.js";
|
|
6
6
|
|
|
7
|
+
// SVG's no-paint keyword is not a valid Canvas color assignment. Assign a real
|
|
8
|
+
// transparent color so the backend cannot retain a previous opaque paint.
|
|
9
|
+
export function resolveCanvasSolidPaint(paint) {
|
|
10
|
+
return paint === "none" ? "rgba(0,0,0,0)" : paint;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
export function applyCanvasFill(context, fill, bounds, graphicId) {
|
|
8
14
|
validateFillPaint(fill, `${graphicId}.fill`);
|
|
9
15
|
if (!isLinearGradientPaint(fill)) {
|
|
10
|
-
context.fillStyle = fill;
|
|
16
|
+
context.fillStyle = resolveCanvasSolidPaint(fill);
|
|
11
17
|
return fill;
|
|
12
18
|
}
|
|
13
19
|
if (typeof context.createLinearGradient !== "function") {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveCanvasSolidPaint } from "./fill.js";
|
|
1
2
|
import { annotateError } from "../../core/diagnostics.js";
|
|
2
3
|
import { drawCircleGraphic } from "./circle.js";
|
|
3
4
|
import { drawLineGraphic } from "./line.js";
|
|
@@ -81,7 +82,7 @@ function fillCanvasBackground(context, id, canvas, width, height) {
|
|
|
81
82
|
);
|
|
82
83
|
}
|
|
83
84
|
context.globalAlpha = 1;
|
|
84
|
-
context.fillStyle = canvas.properties.background;
|
|
85
|
+
context.fillStyle = resolveCanvasSolidPaint(canvas.properties.background);
|
|
85
86
|
context.fillRect(0, 0, width, height);
|
|
86
87
|
}
|
|
87
88
|
|
|
@@ -33,6 +33,7 @@ function drawLine(context, child, collectionId) {
|
|
|
33
33
|
throw new Error(`Graphic "${graphicId}" requires opacity from 0 to 1.`);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
if (strokeWidth === 0) return;
|
|
36
37
|
context.globalAlpha = opacity;
|
|
37
38
|
applyCanvasStroke(context, properties, properties.strokeDash ?? []);
|
|
38
39
|
context.beginPath();
|
|
@@ -82,7 +82,7 @@ function drawPath(context, child, collectionId) {
|
|
|
82
82
|
applyCanvasFill(context, properties.fill, bounds, graphicId);
|
|
83
83
|
context.fill();
|
|
84
84
|
}
|
|
85
|
-
if (hasStroke) {
|
|
85
|
+
if (hasStroke && strokeWidth > 0) {
|
|
86
86
|
applyCanvasStroke(context, properties, strokeDash);
|
|
87
87
|
context.stroke();
|
|
88
88
|
}
|
|
@@ -43,14 +43,17 @@ function drawRect(context, child, collectionId) {
|
|
|
43
43
|
bottom: y + height
|
|
44
44
|
}, graphicId);
|
|
45
45
|
context.fillRect(x, y, width, height);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
// Canvas ignores lineWidth = 0 and would retain a previous nonzero width.
|
|
47
|
+
if (strokeWidth > 0) {
|
|
48
|
+
applyCanvasStroke(context, properties);
|
|
49
|
+
context.beginPath();
|
|
50
|
+
context.moveTo(x, y);
|
|
51
|
+
context.lineTo(x + width, y);
|
|
52
|
+
context.lineTo(x + width, y + height);
|
|
53
|
+
context.lineTo(x, y + height);
|
|
54
|
+
context.lineTo(x, y);
|
|
55
|
+
context.stroke();
|
|
56
|
+
}
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
export function drawRectGraphic(context, id, graphic) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { resolveCanvasSolidPaint } from "./fill.js";
|
|
1
2
|
import { resolveStrokeDetails } from "../../grammar/strokeStyle.js";
|
|
2
3
|
|
|
3
4
|
export function applyCanvasStroke(context, properties, strokeDash = []) {
|
|
4
5
|
const details = resolveStrokeDetails(properties);
|
|
5
|
-
context.strokeStyle = properties.stroke;
|
|
6
|
+
context.strokeStyle = resolveCanvasSolidPaint(properties.stroke);
|
|
6
7
|
context.lineWidth = properties.strokeWidth;
|
|
7
8
|
context.lineCap = details.lineCap;
|
|
8
9
|
context.lineJoin = details.lineJoin;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveCanvasSolidPaint } from "./fill.js";
|
|
1
2
|
import { requireFiniteProperty, requireStringProperty } from "./validation.js";
|
|
2
3
|
import { validateConcreteGraphicProperties } from
|
|
3
4
|
"../../grammar/schemas/concreteGraphic.js";
|
|
@@ -72,15 +73,16 @@ function drawText(context, child, collectionId) {
|
|
|
72
73
|
context.save();
|
|
73
74
|
|
|
74
75
|
try {
|
|
75
|
-
context.fillStyle = fill;
|
|
76
|
+
context.fillStyle = resolveCanvasSolidPaint(fill);
|
|
76
77
|
context.globalAlpha = opacity;
|
|
77
78
|
const rendererFontWeight = normalizeRendererFontWeight(fontWeight);
|
|
78
|
-
context.font = `${rendererFontWeight} ${fontSize}px ${fontFamily}`;
|
|
79
|
+
context.font = `${properties.fontStyle === "italic" ? "italic " : ""}${rendererFontWeight} ${fontSize}px ${fontFamily}`;
|
|
79
80
|
context.textAlign = textAlign;
|
|
80
81
|
context.textBaseline = textBaseline;
|
|
81
82
|
context.translate(x, y);
|
|
82
83
|
context.rotate(rotation);
|
|
83
|
-
context.fillText(text, 0, 0);
|
|
84
|
+
if (properties.lines === undefined) context.fillText(text, 0, 0);
|
|
85
|
+
else for (const line of properties.lines) context.fillText(line.text, line.x, line.y);
|
|
84
86
|
} finally {
|
|
85
87
|
context.restore();
|
|
86
88
|
}
|
package/src/renderers/svg.js
CHANGED
|
@@ -419,13 +419,16 @@ function serializeText(state, graphicId, properties) {
|
|
|
419
419
|
["font-family", fontFamily],
|
|
420
420
|
["font-size", formatNumber(fontSize)],
|
|
421
421
|
["font-weight", normalizeRendererFontWeight(fontWeight)],
|
|
422
|
+
["font-style", properties.fontStyle],
|
|
422
423
|
["text-anchor", TEXT_ANCHORS[textAlign]],
|
|
423
424
|
["dominant-baseline", DOMINANT_BASELINES[textBaseline]],
|
|
424
425
|
["opacity", formatNumber(opacity)],
|
|
425
426
|
["transform", rotation === 0
|
|
426
427
|
? undefined
|
|
427
428
|
: `rotate(${formatNumber(rotationDegrees)} ${formatNumber(x)} ${formatNumber(y)})`]
|
|
428
|
-
], escapeText(text)
|
|
429
|
+
], properties.lines === undefined ? escapeText(text) : properties.lines.map(line =>
|
|
430
|
+
element("tspan", [["x", formatNumber(x + line.x)], ["y", formatNumber(y + line.y)]], escapeText(line.text))
|
|
431
|
+
).join(""));
|
|
429
432
|
}
|
|
430
433
|
|
|
431
434
|
function serializeConcreteGraphic(state, id, graphic) {
|
package/src/selectors/layers.js
CHANGED
|
@@ -37,3 +37,22 @@ export function resolveEligibleLayer(program, {
|
|
|
37
37
|
if (candidates.length === 0) throw annotateError(new Error(`${label} requires an eligible layer.`), { code: "missing-resource", optionPath: targetOption });
|
|
38
38
|
throw annotateError(new Error(`${label} ${targetOption} is ambiguous; provide ${targetOption}.`), { code: "ambiguous-resource", optionPath: targetOption, candidates: candidates.map(layer => layer.id) });
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
// Configured composite plots retain their established owner errors while sharing
|
|
42
|
+
// the explicit/current/unique resolution order.
|
|
43
|
+
export function resolveConfiguredOwner(program, requested, {
|
|
44
|
+
config, operation, kind, article = "a", materialized = false, missing, ambiguous
|
|
45
|
+
}, validateUserId) {
|
|
46
|
+
const eligible = program.semanticSpec.layers.filter(layer => {
|
|
47
|
+
const value = program.markConfigs[layer.id]?.[config];
|
|
48
|
+
return materialized ? value?.materialized === true : value !== undefined;
|
|
49
|
+
});
|
|
50
|
+
const id = requested === undefined ? program.context.currentMark
|
|
51
|
+
: validateUserId(requested, `${kind[0].toUpperCase() + kind.slice(1)}-plot owner id`);
|
|
52
|
+
const owner = eligible.find(layer => layer.id === id);
|
|
53
|
+
if (owner !== undefined) return owner;
|
|
54
|
+
if (requested !== undefined) throw new Error(`Unknown ${kind}-plot owner "${id}".`);
|
|
55
|
+
if (eligible.length === 1) return eligible[0];
|
|
56
|
+
if (eligible.length === 0) throw new Error(missing ?? `${operation} requires ${article} ${kind} plot.`);
|
|
57
|
+
throw new Error(ambiguous ?? `${operation} target is ambiguous; provide target.`);
|
|
58
|
+
}
|
package/src/theme/defaults.js
CHANGED
|
@@ -2,6 +2,9 @@ import { DEFAULT_FONT_FAMILY } from "../core/font.js";
|
|
|
2
2
|
export { DEFAULT_FONT_FAMILY };
|
|
3
3
|
|
|
4
4
|
export const DEFAULT_POINT_RADIUS = 3;
|
|
5
|
+
export const AXIS_LABEL_FONT_SIZES = Object.freeze({ cartesian: 12, polar: 11, parallel: 11 });
|
|
6
|
+
export const AXIS_TITLE_FONT_SIZE = 13;
|
|
7
|
+
export const DEFAULT_VERTICAL_LEGEND_ITEM_GAP = 18;
|
|
5
8
|
|
|
6
9
|
export const DEFAULT_COLORS = Object.freeze({
|
|
7
10
|
mark: "#4c78a8",
|
|
@@ -42,7 +45,8 @@ export const THEME_TOKEN_KEYS = Object.freeze([
|
|
|
42
45
|
"referenceBand",
|
|
43
46
|
"gradientCenter",
|
|
44
47
|
"highlight",
|
|
45
|
-
"fontFamily"
|
|
48
|
+
"fontFamily", "axisLabel", "axisLabelFontFamily", "axisTitleFontFamily",
|
|
49
|
+
"axisLabelFontSize", "axisTitleFontSize"
|
|
46
50
|
]);
|
|
47
51
|
|
|
48
52
|
const THEME_DEFINITION_KEYS = Object.freeze(["base", "tokens"]);
|
|
@@ -120,7 +124,9 @@ function normalizeThemeTokens(tokens) {
|
|
|
120
124
|
validateKeys(tokens, THEME_TOKEN_KEYS, "applyTheme theme.tokens");
|
|
121
125
|
const normalized = {};
|
|
122
126
|
for (const [key, value] of Object.entries(tokens)) {
|
|
123
|
-
|
|
127
|
+
if (key.endsWith("FontSize")) {
|
|
128
|
+
if (!Number.isFinite(value) || value <= 0) throw new RangeError(`applyTheme theme.tokens.${key} must be positive and finite.`);
|
|
129
|
+
} else validateNonEmptyString(value, `applyTheme theme.tokens.${key}`);
|
|
124
130
|
normalized[key] = value;
|
|
125
131
|
}
|
|
126
132
|
return own(normalized);
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Must match package.json; enforced by the snapshot producer-version test.
|
|
2
|
-
export const packageVersion = "0.0.
|
|
2
|
+
export const packageVersion = "0.0.18";
|
package/types/basic.d.ts
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ChartProgram, CompositionOptions } from "./program.js";
|
|
2
2
|
|
|
3
3
|
export type {
|
|
4
|
+
ArcInnerRadius,
|
|
5
|
+
PieRadiusChannel,
|
|
4
6
|
TextMetricFontWeight,
|
|
5
7
|
TextMeasurement,
|
|
6
8
|
TextMetricsProfile,
|
|
@@ -27,6 +29,7 @@ export type {
|
|
|
27
29
|
DisplayLabelOptions,
|
|
28
30
|
BarWidthOptions,
|
|
29
31
|
BoxPlotOptions,
|
|
32
|
+
BoxPlotSummaryFields,
|
|
30
33
|
BinnedHeatmapColorOptions,
|
|
31
34
|
BinnedHeatmapOptions,
|
|
32
35
|
BinnedHeatmapPositionChannel,
|
|
@@ -117,6 +120,7 @@ export type {
|
|
|
117
120
|
CreateGuidesOptions,
|
|
118
121
|
CreateGridOptions,
|
|
119
122
|
CreateBarPlotOptions,
|
|
123
|
+
BarRangePositionChannel,
|
|
120
124
|
CreateHeatmapOptions,
|
|
121
125
|
CreateHistogramOptions,
|
|
122
126
|
CreatePiePlotOptions,
|
|
@@ -166,6 +170,7 @@ export type {
|
|
|
166
170
|
StripPixelJitterOptions,
|
|
167
171
|
CreateParallelCoordinatesOptions,
|
|
168
172
|
CreateScatterPlotOptions,
|
|
173
|
+
CreateTextPlotOptions,
|
|
169
174
|
CreateIntervalPlotOptions,
|
|
170
175
|
CreateRegressionPlotOptions,
|
|
171
176
|
CreateDotPlotOptions,
|
|
@@ -241,6 +246,7 @@ export type {
|
|
|
241
246
|
EditHorizonOptions,
|
|
242
247
|
EditCompositionLayoutOptions,
|
|
243
248
|
EditFacetHeadersOptions,
|
|
249
|
+
FacetHeadersOptions,
|
|
244
250
|
EditAxisOptions,
|
|
245
251
|
EditGradientPlotOptions,
|
|
246
252
|
EditViolinPlotOptions,
|
|
@@ -375,6 +381,7 @@ export type {
|
|
|
375
381
|
ScaleType,
|
|
376
382
|
ShapeScaleOptions,
|
|
377
383
|
SizeScaleOptions,
|
|
384
|
+
CategoricalSizeScaleOptions,
|
|
378
385
|
StackMode,
|
|
379
386
|
StackDataMode,
|
|
380
387
|
StackDataOptions,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BasicChartProgram } from "./basic.js";
|
|
2
|
+
import type { ChartProgram, DatasetSchema } from "./program.js";
|
|
3
|
+
export type ProgramInput = ChartProgram | BasicChartProgram;
|
|
4
|
+
export interface ResourceRef { readonly kind: "data" | "mark" | "scale" | "coordinate" | "guide" | "canvas" | "composition"; readonly id: string; readonly childPath?: readonly string[]; }
|
|
5
|
+
export interface InspectionTarget { readonly kind: "data" | "mark" | "scale" | "coordinate" | "guide"; readonly id: string; readonly childPath?: readonly string[]; }
|
|
6
|
+
export interface Finding { readonly severity: "error" | "warning" | "info"; readonly code: string; readonly reason: string; readonly message: string; readonly resource?: ResourceRef; readonly optionPath?: string; }
|
|
7
|
+
export interface CheckResult { readonly name: string; readonly status: "not_run" | "passed" | "failed" | "not_applicable"; readonly evidencePaths: readonly string[]; readonly findings: readonly Finding[]; }
|
|
8
|
+
export interface ParameterDefinition { readonly path: string; readonly kind: "field" | "enum" | "number" | "string" | "boolean" | "range" | "array" | "object" | "resource"; readonly required: boolean; readonly active: boolean; readonly choices?: readonly (string | number | boolean | null)[]; readonly minimum?: number; readonly maximum?: number; readonly unit?: string; readonly fieldRole?: string; }
|
|
9
|
+
export interface ActionDescription { readonly version: 1; readonly action: string; readonly applicability: "supported" | "needs-input" | "incompatible" | "unsupported" | "unverified"; readonly parameterDefinitions: readonly ParameterDefinition[]; readonly requirements: readonly { readonly reason: string; readonly optionPath?: string }[]; readonly checks: readonly CheckResult[]; readonly findings: readonly Finding[]; }
|
|
10
|
+
export interface Change { readonly resource: ResourceRef; readonly operation: "add" | "remove" | "modify"; readonly effect: "data" | "binding" | "structure" | "scale" | "guide" | "style"; readonly roles: readonly string[]; }
|
|
11
|
+
export interface ProgramComparison { readonly version: 1; readonly equivalence: "equal" | "different" | "unknown"; readonly completeProgramComparison: boolean; readonly changes: readonly Change[]; readonly affected: readonly ResourceRef[]; readonly findings: readonly Finding[]; }
|
|
12
|
+
export interface OwnerInspection { readonly owner: ResourceRef; readonly grain: "item" | "series" | "composite" | "unknown"; readonly rawRows: number | null; readonly logicalDataItems: number | null; readonly drawablePrimitives: number | null; readonly visibleCandidates: number | null; readonly flags: readonly { readonly reason: "opacity-zero" | "zero-size" | "outside-clip" | "missing-skipped"; readonly count: number | null }[]; readonly bounds: Readonly<{x:number;y:number;width:number;height:number}> | null; readonly sourceRefs: readonly ResourceRef[]; readonly checks: readonly CheckResult[]; }
|
|
13
|
+
export interface ProgramInspection { readonly version: 1; readonly views: readonly OwnerInspection[]; readonly calculations: readonly unknown[]; readonly checks: readonly CheckResult[]; readonly findings: readonly Finding[]; readonly coverage: Readonly<{partial:boolean;unsupported:readonly ResourceRef[]}>; }
|
|
14
|
+
export function getDatasetSchema(program: ProgramInput, options: {readonly data:string;readonly childPath?:readonly string[]}): Readonly<{data:string;schema:DatasetSchema}>;
|
|
15
|
+
export function describeAction(program: ProgramInput, options: {readonly action:string;readonly target?:InspectionTarget;readonly options?:Readonly<Record<string,unknown>>}): ActionDescription;
|
|
16
|
+
export function comparePrograms(before: ProgramInput, after: ProgramInput, options?: {readonly target?:InspectionTarget}): ProgramComparison;
|
|
17
|
+
export function inspectProgram(program: ProgramInput, options?: {readonly target?:InspectionTarget}): ProgramInspection;
|