ggaction 0.0.21 → 0.0.22
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 +73 -0
- package/README.md +3 -2
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +6 -2
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +1 -0
- package/src/actions/boxPlots/components.js +1 -96
- package/src/actions/boxPlots/create.js +6 -1
- package/src/actions/boxPlots/edit.js +8 -0
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +1 -1
- package/src/actions/canvas/actions.js +2 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +2 -1
- package/src/actions/canvas/plotOrder.js +14 -0
- package/src/actions/charts/area.js +1 -1
- package/src/actions/charts/bar.js +4 -4
- package/src/actions/charts/density.js +1 -1
- package/src/actions/charts/ecdf.js +1 -1
- package/src/actions/charts/endpoints.js +54 -5
- package/src/actions/charts/heatmap.js +1 -1
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +1 -1
- package/src/actions/charts/index.js +2 -1
- package/src/actions/charts/interval-regression.js +1 -1
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/radar.js +63 -3
- package/src/actions/charts/raincloud.js +37 -6
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/scatter.js +3 -3
- package/src/actions/charts/text.js +1 -1
- package/src/actions/composition/actions.js +10 -3
- package/src/actions/coordinates/actions.js +2 -0
- package/src/actions/data/basic.js +6 -0
- package/src/actions/data/derived.js +6 -2
- package/src/actions/data/edit.js +21 -35
- package/src/actions/data/filter.js +1 -376
- package/src/actions/data/flatten.js +19 -0
- package/src/actions/data/index.js +6 -0
- package/src/actions/data/lookup.js +50 -0
- package/src/actions/data/raincloudFilter.js +56 -0
- package/src/actions/data/shared.js +10 -0
- package/src/actions/distributions/revision.js +3 -1
- package/src/actions/encodings/angle.js +2 -1
- package/src/actions/encodings/appearance.js +38 -13
- package/src/actions/encodings/channels.js +14 -3
- package/src/actions/encodings/color/continuous.js +26 -10
- package/src/actions/encodings/color/index.js +50 -20
- package/src/actions/encodings/color/policy.js +57 -4
- package/src/actions/encodings/parallel.js +4 -0
- package/src/actions/encodings/position/policies/bar.js +10 -6
- package/src/actions/encodings/position/policies/line.js +1 -152
- package/src/actions/encodings/position/resolve.js +4 -1
- package/src/actions/encodings/ranged.js +46 -0
- package/src/actions/encodings/remove.js +1 -1
- package/src/actions/encodings/ruleAppearance.js +7 -6
- package/src/actions/encodings/shared.js +14 -2
- package/src/actions/encodings/stroke.js +20 -14
- package/src/actions/encodings/strokeDash.js +9 -11
- package/src/actions/encodings/text.js +30 -2
- package/src/actions/errorBars/edit.js +24 -4
- package/src/actions/facets/actions.js +1 -793
- package/src/actions/facets/derive.js +34 -5
- package/src/actions/facets/replay.js +1 -38
- package/src/actions/gradientPlots/components.js +21 -9
- package/src/actions/gradientPlots/create.js +4 -2
- package/src/actions/gradientPlots/edit.js +18 -1
- package/src/actions/gradientPlots/materialize.js +35 -36
- package/src/actions/gradientPlots/options.js +1 -1
- package/src/actions/gradientPlots/paint.js +13 -7
- package/src/actions/guides/applicability.js +2 -0
- package/src/actions/guides/axes/axes.js +39 -0
- package/src/actions/guides/axes/axis.js +9 -5
- package/src/actions/guides/axes/edit.js +11 -8
- package/src/actions/guides/axes/labels.js +24 -8
- package/src/actions/guides/axes/parallel/policy.js +3 -5
- package/src/actions/guides/axes/parallel/resolve.js +4 -3
- package/src/actions/guides/axes/policy.js +22 -0
- package/src/actions/guides/axes/tickGroups.js +4 -2
- package/src/actions/guides/axes/ticks.js +22 -4
- package/src/actions/guides/axes/titles.js +19 -6
- package/src/actions/guides/facadeAxes.js +3 -0
- package/src/actions/guides/grids/grid.js +3 -1
- package/src/actions/guides/grids/resolve.js +7 -11
- package/src/actions/guides/guides.js +21 -1
- package/src/actions/guides/legends/categorical/actions.js +1 -515
- package/src/actions/guides/legends/categorical/layout.js +5 -2
- package/src/actions/guides/legends/categorical/options.js +4 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -3
- package/src/actions/guides/legends/categorical/symbols.js +3 -3
- package/src/actions/guides/legends/colorSize.js +2 -0
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/guides/legends/continuous/gradient.js +1 -360
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +15 -1
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +1 -1
- package/src/actions/guides/legends/lane.js +1 -1
- package/src/actions/guides/legends/sampling.js +1 -140
- package/src/actions/guides/legends/strokeWidth.js +3 -2
- package/src/actions/guides/legends/transition.js +19 -2
- package/src/actions/guides/polar/axes/facade.js +5 -4
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/resolve.js +2 -1
- package/src/actions/guides/tickValues.js +7 -7
- package/src/actions/marks/arc/actions.js +5 -7
- package/src/actions/marks/area/actions.js +9 -3
- package/src/actions/marks/bar/create.js +4 -2
- package/src/actions/marks/bar/edit.js +9 -6
- package/src/actions/marks/bar/materialize.js +5 -3
- package/src/actions/marks/dependentReferences.js +87 -0
- package/src/actions/marks/index.js +2 -0
- package/src/actions/marks/lifecycle.js +2 -1
- package/src/actions/marks/line/actions.js +24 -6
- package/src/actions/marks/line/materialize.js +7 -3
- package/src/actions/marks/point/create.js +5 -4
- package/src/actions/marks/point/edit.js +6 -3
- package/src/actions/marks/point/jitter.js +6 -6
- package/src/actions/marks/point/materialize.js +36 -39
- package/src/actions/marks/rect/actions.js +16 -4
- package/src/actions/marks/references.js +183 -35
- package/src/actions/marks/rule/actions.js +20 -9
- package/src/actions/marks/shared.js +2 -1
- package/src/actions/marks/text/actions.js +10 -3
- package/src/actions/marks/tick/actions.js +11 -2
- package/src/actions/primitives/editGraphics.js +28 -3
- package/src/actions/primitives/index.js +9 -0
- package/src/actions/primitives/semanticAction.js +13 -7
- package/src/actions/primitives/semanticValidation/layer.js +20 -0
- package/src/actions/primitives/semanticValidation/scale.js +8 -1
- package/src/actions/scales/channels.js +1 -1
- package/src/actions/scales/consumers/common.js +19 -1
- package/src/actions/scales/consumers/families.js +2 -2
- package/src/actions/scales/consumers/seriesLayout.js +4 -3
- package/src/actions/scales/edit.js +11 -0
- package/src/actions/scales/editPolicy.js +5 -3
- package/src/actions/scales/preview.js +2 -1
- package/src/actions/scales/quantitativeColor.js +2 -4
- package/src/actions/selection/actions.js +10 -1
- package/src/actions/theme/reconcile.js +37 -9
- package/src/core/compositionState.js +21 -4
- package/src/core/datasetReferences.js +17 -0
- package/src/core/immutable.js +1 -0
- package/src/core/resourceReferences.js +5 -3
- package/src/core/validation.js +4 -0
- package/src/core/vocabulary.js +24 -2
- package/src/grammar/aggregate.js +1 -0
- package/src/grammar/arcs.js +1 -1
- package/src/grammar/areaSeries.js +1 -1
- package/src/grammar/axisTextPlacement.js +1 -1
- package/src/grammar/bars/policy.js +2 -2
- package/src/grammar/datasetDependencies.js +97 -0
- package/src/grammar/datasetSchema.js +31 -1
- package/src/grammar/facetHeaders.js +1 -0
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/legendIntent.js +6 -2
- package/src/grammar/facets/scales.js +27 -7
- package/src/grammar/flatten.js +85 -0
- package/src/grammar/histogram.js +13 -1
- package/src/grammar/horizon.js +1 -494
- package/src/grammar/jitter.js +2 -2
- package/src/grammar/legendSpacing.js +45 -0
- package/src/grammar/lineMissing.js +90 -0
- package/src/grammar/lineSeries.js +1 -376
- package/src/grammar/lookup.js +97 -0
- package/src/grammar/markFilter.js +4 -2
- package/src/grammar/markLabels.js +8 -3
- package/src/grammar/numeric.js +1 -1
- package/src/grammar/pathCommands.js +1 -1
- package/src/grammar/pathSeries.js +1 -109
- package/src/grammar/pointPolygon.js +81 -0
- package/src/grammar/pointShapes.js +15 -15
- package/src/grammar/rects.js +1 -1
- package/src/grammar/regression/derive.js +1 -195
- package/src/grammar/scales/appearance.js +1 -1
- package/src/grammar/scales/color.js +1 -1
- package/src/grammar/scales/colorConsumers.js +1 -63
- package/src/grammar/scales/continuous.js +58 -1
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +3 -3
- package/src/grammar/schemas/graphicBounds.js +40 -12
- package/src/grammar/schemas/graphicClip.js +23 -0
- package/src/grammar/schemas/graphicTree.js +16 -0
- package/src/grammar/statisticalReference.js +22 -0
- package/src/grammar/text.js +65 -1
- package/src/grammar/ticks.js +92 -1
- package/src/grammar/transformTopology.js +3 -1
- package/src/grammar/transforms.js +24 -0
- package/src/inspection.js +1 -495
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -1
- package/src/layout/facetContentFlow.js +101 -0
- package/src/layout/facetHeaderTitles.js +1 -0
- package/src/layout/facetTopLane.js +29 -0
- package/src/layout/facets.js +1 -1
- package/src/layout/legendGroup.js +1 -1
- package/src/layout/legendItems.js +26 -10
- package/src/layout/legendSpacing.js +66 -0
- package/src/layout/plotDrawOrder.js +17 -0
- package/src/materialization/bars/histogram.js +8 -2
- package/src/materialization/bars/resolve.js +1 -1
- package/src/materialization/composition.js +1 -0
- package/src/materialization/compositionSnapshot.js +22 -2
- package/src/materialization/dataProvenance.js +1 -1
- package/src/materialization/facetGuides/layout.js +1 -1
- package/src/materialization/facetGuides/placement.js +23 -11
- package/src/materialization/facetGuides/preparation.js +43 -24
- package/src/materialization/facetHeaders.js +1 -1
- package/src/materialization/facetTopLane.js +25 -0
- package/src/materialization/facets.js +1 -1
- package/src/materialization/graphicHierarchy.js +5 -0
- package/src/materialization/graphicTranslation.js +2 -0
- package/src/materialization/guides/layout.js +1 -1
- package/src/materialization/guides/resources.js +1 -1
- package/src/materialization/legends.js +1 -1
- package/src/materialization/markClip.js +26 -0
- package/src/materialization/marks/capabilities.js +3 -2
- package/src/materialization/marks/grouping.js +7 -5
- package/src/materialization/marks/index.js +4 -2
- package/src/materialization/marks/policies.js +4 -0
- package/src/materialization/plotDrawOrder.js +44 -0
- package/src/materialization/rect.js +11 -3
- package/src/materialization/scaleGuideDependencies.js +13 -5
- package/src/materialization/selection/items/common.js +1 -1
- package/src/materialization/selection/items/path.js +1 -0
- package/src/materialization/selection/policies/index.js +2 -1
- package/src/materialization/text.js +10 -5
- package/src/mcp/cli.js +0 -0
- package/src/persistence/validation.js +108 -18
- package/src/persistence.js +4 -2
- package/src/renderers/canvas/index.js +16 -0
- package/src/renderers/canvas/isolated.js +11 -1
- package/src/renderers/canvas/native.js +7 -0
- package/src/renderers/canvas/scene.js +66 -0
- package/src/renderers/canvas/text.js +3 -2
- package/src/renderers/svg.js +1 -617
- package/src/renderers/svgSerialization.js +643 -0
- package/src/renderers/transitionScene.js +67 -0
- package/src/scene.js +89 -0
- package/src/selectors/axes.js +1 -1
- package/src/selectors/datasets.js +1 -0
- package/src/selectors/grids.js +9 -0
- package/src/selectors/markClip.js +28 -0
- package/src/selectors/raincloud.js +7 -0
- package/src/version.js +1 -1
- package/types/basic.d.ts +7 -0
- package/types/index.d.ts +8 -0
- package/types/program.d.ts +270 -60
- package/types/scene.d.ts +35 -0
|
@@ -5,11 +5,10 @@ import {
|
|
|
5
5
|
resolveMarkLabelPlacement
|
|
6
6
|
} from "../layout/labels.js";
|
|
7
7
|
import { resolveMarkLabelValues } from "../grammar/markLabels.js";
|
|
8
|
-
import { formatTextValue, resolveTextLines } from "../grammar/text.js";
|
|
8
|
+
import { formatTextValue, resolveTextLines, resolveTextOffsetDataset, textOffsetValue } from "../grammar/text.js";
|
|
9
9
|
import { normalizePositionDatum } from "../grammar/positionDatum.js";
|
|
10
10
|
import { mapOrdinalPositionValues } from "../grammar/scales/index.js";
|
|
11
11
|
import { findDataset } from "../selectors/datasets.js";
|
|
12
|
-
import { applyItemMissingPolicy } from "../grammar/itemMissing.js";
|
|
13
12
|
import { findCoordinate } from "../selectors/coordinates.js";
|
|
14
13
|
import { findLayer } from "../selectors/layers.js";
|
|
15
14
|
import { unionConcreteGraphicBounds } from
|
|
@@ -427,8 +426,9 @@ function resolveRowTextItems(program, layer, config) {
|
|
|
427
426
|
if (sourceDataset === undefined) {
|
|
428
427
|
throw new Error(`Text mark "${layer.id}" requires an existing dataset.`);
|
|
429
428
|
}
|
|
430
|
-
const dataset =
|
|
431
|
-
const encodings = [layer.encoding?.x, layer.encoding?.y, layer.encoding?.text, layer.encoding?.color
|
|
429
|
+
const dataset = resolveTextOffsetDataset(layer, sourceDataset);
|
|
430
|
+
const encodings = [layer.encoding?.x, layer.encoding?.y, layer.encoding?.text, layer.encoding?.color,
|
|
431
|
+
layer.encoding?.dx, layer.encoding?.dy];
|
|
432
432
|
const length = encodings.some(encoding => encoding !== undefined && Object.hasOwn(encoding, "field"))
|
|
433
433
|
? dataset.values.length
|
|
434
434
|
: 1;
|
|
@@ -465,7 +465,12 @@ function resolveRowTextItems(program, layer, config) {
|
|
|
465
465
|
const fills = resolveRowEncodingValues(program, layer, dataset, "color");
|
|
466
466
|
return rows.flatMap((row, index) => {
|
|
467
467
|
const concrete = concreteItem(
|
|
468
|
-
|
|
468
|
+
{
|
|
469
|
+
...config,
|
|
470
|
+
...(fills === undefined ? {} : { fill: fills[index] }),
|
|
471
|
+
dx: textOffsetValue(layer.encoding?.dx, row, config.dx),
|
|
472
|
+
dy: textOffsetValue(layer.encoding?.dy, row, config.dy)
|
|
473
|
+
},
|
|
469
474
|
{ x: x[index], y: y[index] },
|
|
470
475
|
contentValue(layer.encoding.text, row),
|
|
471
476
|
layer.encoding.text.format
|
package/src/mcp/cli.js
CHANGED
|
File without changes
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
import { validateGraphicClip } from "../grammar/schemas/graphicClip.js";
|
|
2
|
+
import { validatePointShape } from "../grammar/pointShapes.js";
|
|
3
|
+
import {deriveLookupRows,sameLookupValue} from "../grammar/lookup.js";
|
|
4
|
+
import {deriveTransformSchema,datasetSchemaContext} from "../grammar/datasetSchema.js";
|
|
5
|
+
import {orderedDatasetAncestors} from "../grammar/datasetDependencies.js";
|
|
6
|
+
import {validateLegendSpacing} from "../grammar/legendSpacing.js";
|
|
7
|
+
import { requestedMarkClip, validateMarkClipCoordinate } from "../selectors/markClip.js";
|
|
1
8
|
import { validateAuthoredFacetLegend } from "../grammar/facets/legendIntent.js";
|
|
9
|
+
import { validateFacetHeaderRotations } from "../grammar/facetHeaders.js";
|
|
2
10
|
import { AXIS_RESOURCE_CHANNELS } from "../core/vocabulary.js";
|
|
3
11
|
import { cartesianAxisOwners, assertCartesianAxisSideAvailable } from "../selectors/axes.js";
|
|
4
12
|
import { axisGraphicIds } from "../selectors/axes.js";
|
|
@@ -20,16 +28,19 @@ import { validateGraphicItemLimit } from "../core/validation.js";
|
|
|
20
28
|
import { LEGEND_TITLE_STYLE_KEYS, validateLegendTitleAlignment } from "../grammar/legendTitleStyle.js";
|
|
21
29
|
import { validateGuideDash, normalizeGuideDashMap } from "../grammar/guideDash.js";
|
|
22
30
|
import { validateAxisTextPlacement } from "../grammar/axisTextPlacement.js";
|
|
31
|
+
import { validateTickSelectionRounding, axisTickSelection } from "../grammar/ticks.js";
|
|
23
32
|
import { validateAreaPaintConfig } from "../grammar/areaPaint.js";
|
|
24
33
|
import { validateOptionObject, validateKeys, validatePositiveFinite, validateNonNegativeFinite, validateNonEmptyString, validateGeneratedItemLimit } from "../core/validation.js";
|
|
25
34
|
import { validateRegressionFitStatistics, requireRegressionStatisticsSource, regressionStatisticsRows } from "../grammar/regression/statistics.js";
|
|
26
35
|
import { resolveGraphicItemCapacity } from "../selectors/graphicCapacity.js";
|
|
27
36
|
import { isContinuousColorScaleType } from "../grammar/scales/types.js";
|
|
37
|
+
import { validateTickColorChannels } from "../grammar/scales/colorConsumers.js";
|
|
28
38
|
import { normalizeColorInterpolationOptions } from "../grammar/scales/color.js";
|
|
29
39
|
import { normalizeTransformParameters } from "../grammar/scales/transformed.js";
|
|
30
40
|
import { validateLegendGroupState } from "../grammar/legendGroups.js";
|
|
31
41
|
import { normalizeGradientDirection, validateLegendGradient } from "../grammar/legendGradient.js";
|
|
32
42
|
import { normalizeTextMetricProfile } from "../core/textMetricProfile.js";
|
|
43
|
+
import { resolveTextOffsetDataset } from "../grammar/text.js";
|
|
33
44
|
import { isPlainObject } from "../core/immutable.js";
|
|
34
45
|
import { validateUserId } from "../core/identifiers.js";
|
|
35
46
|
import { collectAllResourceReferences } from "../core/resourceReferences.js";
|
|
@@ -48,6 +59,21 @@ export const STATE_KEYS = Object.freeze([
|
|
|
48
59
|
"children", "compositionSpec", "context", "trace", "actionStack"
|
|
49
60
|
]);
|
|
50
61
|
|
|
62
|
+
function validateStoredColorLegendSampling(program, config, path) {
|
|
63
|
+
requireObject(config.sampling, path);
|
|
64
|
+
const sample = config.sampling;
|
|
65
|
+
exactKeys(sample, sample.mode === "values" ? ["mode", "values", "count"] : ["mode", "count"], path);
|
|
66
|
+
if (!["auto", "values"].includes(sample.mode)) throw invalidSnapshot("Invalid color legend sample mode.", path);
|
|
67
|
+
if (!Number.isInteger(sample.count) || sample.count < 2) throw invalidSnapshot("Invalid color legend sample count.", path);
|
|
68
|
+
validateGeneratedItemLimit(sample.count, "Color legend sample count");
|
|
69
|
+
const scale = program.resolvedScales[config.scale];
|
|
70
|
+
if (!isContinuousColorScaleType(scale?.type)) throw invalidSnapshot("Sampled color legend requires a continuous color scale.", path);
|
|
71
|
+
if (sample.mode === "values") {
|
|
72
|
+
requireArray(sample.values, path);
|
|
73
|
+
if (!sample.values.length || sample.values.length > 100 || sample.values.some((value, index) => !Number.isFinite(value) || index > 0 && value <= sample.values[index - 1] || value < Math.min(...scale.domain) || value > Math.max(...scale.domain) || scale.type === "log" && value <= 0)) throw invalidSnapshot("Invalid exact color legend samples.", path);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
51
77
|
export function requireObject(value, path) {
|
|
52
78
|
if (!isPlainObject(value)) throw invalidSnapshot(`${path} must be a plain object.`, path);
|
|
53
79
|
}
|
|
@@ -79,9 +105,10 @@ export function validateGraphic(graphic, path = "payload.graphicSpec") {
|
|
|
79
105
|
if (!id.length) throw invalidSnapshot("Graphic IDs must be non-empty.", path);
|
|
80
106
|
requireObject(object, `${path}.objects.${id}`);
|
|
81
107
|
validateGraphicType(object.type);
|
|
82
|
-
if (Object.keys(object).some(key => !["type", "properties", "items", "children"].includes(key))) {
|
|
108
|
+
if (Object.keys(object).some(key => !["type", "properties", "items", "children", "clip"].includes(key))) {
|
|
83
109
|
throw invalidSnapshot(`Unknown graphic member at ${id}.`, path);
|
|
84
110
|
}
|
|
111
|
+
if (object.clip !== undefined) validateGraphicClip(object.clip, object.type);
|
|
85
112
|
if (object.properties !== undefined) validateConcreteGraphicProperties(object.type, object.properties);
|
|
86
113
|
if (object.items !== undefined) {
|
|
87
114
|
if (object.type === "canvas" || object.properties !== undefined) throw invalidSnapshot(`Invalid item owner "${id}".`, path);
|
|
@@ -143,7 +170,7 @@ function validateSemanticProperties(program, value, property) {
|
|
|
143
170
|
validateStoredSemanticValue(program, parsed, value);
|
|
144
171
|
}
|
|
145
172
|
|
|
146
|
-
export function validateProgramState(program, path) {
|
|
173
|
+
export function validateProgramState(program, path, { allowMissingLookupSchemas = false } = {}) {
|
|
147
174
|
const semantic = program.semanticSpec;
|
|
148
175
|
exactKeys(semantic, ["datasets", "layers", "scales", "coordinates", "guides", "title"], `${path}.semanticSpec`);
|
|
149
176
|
const resources = new Map();
|
|
@@ -161,6 +188,12 @@ export function validateProgramState(program, path) {
|
|
|
161
188
|
}
|
|
162
189
|
resources.set(referenceKind, entries);
|
|
163
190
|
for (const resource of entries.values()) {
|
|
191
|
+
if (kind === "layer") validateTickColorChannels(resource);
|
|
192
|
+
if (kind === "layer" && ["dx", "dy"].some(channel => resource.encoding?.[channel] !== undefined)) {
|
|
193
|
+
const dataset = semantic.datasets.find(item => item.id === resource.data);
|
|
194
|
+
if (dataset === undefined) throw new Error("Numeric text offsets require an existing dataset.");
|
|
195
|
+
resolveTextOffsetDataset(resource, dataset);
|
|
196
|
+
}
|
|
164
197
|
if (kind === "scale") {
|
|
165
198
|
normalizeColorInterpolationOptions(resource.interpolate, resource);
|
|
166
199
|
if (resource.type === "pow") normalizeTransformParameters("pow", {
|
|
@@ -181,6 +214,13 @@ export function validateProgramState(program, path) {
|
|
|
181
214
|
if (program.compositionSpec?.type === "facet" && isPlainObject(program.compositionSpec.facet.guides.legend)) {
|
|
182
215
|
validateAuthoredFacetLegend(program, program.compositionSpec.facet.guides.legend);
|
|
183
216
|
}
|
|
217
|
+
if (program.compositionSpec?.type === "facet") {
|
|
218
|
+
const headers = program.materializationConfigs.facets?.[program.compositionSpec.id]?.headers;
|
|
219
|
+
if (headers !== undefined) validateFacetHeaderRotations(headers);
|
|
220
|
+
if (headers?.row?.title !== undefined && program.compositionSpec.facet.grid === undefined) {
|
|
221
|
+
throw new TypeError("Facet row field title requires a row-column facet grid.");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
184
224
|
if (program.materializationConfigs.textMetrics !== undefined) normalizeTextMetricProfile(program.materializationConfigs.textMetrics);
|
|
185
225
|
for (const config of [
|
|
186
226
|
...Object.values(program.guideConfigs.axis ?? {}).map(axis => axis.ticks),
|
|
@@ -189,17 +229,54 @@ export function validateProgramState(program, path) {
|
|
|
189
229
|
if (config?.strokeDash !== undefined) validateGuideDash(config.strokeDash);
|
|
190
230
|
if (config?.strokeDashMap !== undefined) normalizeGuideDashMap(config.strokeDashMap, { reset: false });
|
|
191
231
|
}
|
|
192
|
-
|
|
232
|
+
const cartesianOwners = cartesianAxisOwners(program);
|
|
233
|
+
for (const [id, axis] of Object.entries(program.guideConfigs.axis ?? {})) {
|
|
234
|
+
for (const config of [axis?.ticks, axis?.labels]) {
|
|
235
|
+
if (config === undefined || !Object.hasOwn(config, "rounding")) continue;
|
|
236
|
+
if (!cartesianOwners.some(owner => owner.id === id)) {
|
|
237
|
+
throw invalidSnapshot("Tick rounding requires a Cartesian axis owner.", path);
|
|
238
|
+
}
|
|
239
|
+
validateTickSelectionRounding(config, program.resolvedScales[config.scale]?.type);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
for (const owner of cartesianOwners) {
|
|
193
243
|
const channel = owner.id;
|
|
194
244
|
const axis = owner.config;
|
|
245
|
+
if (axis?.ticks && axis?.labels &&
|
|
246
|
+
(Object.hasOwn(axis.ticks, "rounding") || Object.hasOwn(axis.labels, "rounding")) &&
|
|
247
|
+
JSON.stringify(axisTickSelection(axis.ticks)) !== JSON.stringify(axisTickSelection(axis.labels))) {
|
|
248
|
+
throw invalidSnapshot("Stored tick rounding conflicts with axis labels.", path);
|
|
249
|
+
}
|
|
195
250
|
if (axis?.frame !== undefined) {
|
|
196
|
-
|
|
197
|
-
validateNonNegativeFinite(axis.frame.offset, "Axis frame offset");
|
|
251
|
+
validateOptionObject(axis.frame, ["offset", "underlay"], `Stored axis "${channel}" frame`, { allowEmpty: false });
|
|
252
|
+
if (Object.hasOwn(axis.frame, "offset")) validateNonNegativeFinite(axis.frame.offset, "Axis frame offset");
|
|
253
|
+
if (Object.hasOwn(axis.frame, "underlay") && axis.frame.underlay !== true) {
|
|
254
|
+
throw new Error("Stored axis underlay must be canonical true intent.");
|
|
255
|
+
}
|
|
198
256
|
}
|
|
199
257
|
if (axis?.labels !== undefined) validateAxisTextPlacement(axis.labels);
|
|
200
258
|
if (axis?.title !== undefined) validateAxisTextPlacement(axis.title, { title: true });
|
|
201
259
|
}
|
|
260
|
+
for (const config of Object.values(program.guideConfigs.grid ?? {})) {
|
|
261
|
+
if (!Object.hasOwn(config, "rounding")) continue;
|
|
262
|
+
validateTickSelectionRounding(config, program.resolvedScales[config.scale]?.type);
|
|
263
|
+
const owners = cartesianOwners.map(owner => owner.config?.ticks)
|
|
264
|
+
.filter(ticks => ticks?.scale === config.scale);
|
|
265
|
+
if (config.inferredValues !== true || config.rounding !== "nearest" || owners.length === 0 ||
|
|
266
|
+
owners.some(ticks => JSON.stringify(axisTickSelection(ticks)) !== JSON.stringify(axisTickSelection(config)))) {
|
|
267
|
+
throw invalidSnapshot("Stored grid rounding requires its matching inferred tick policy.", path);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const border = program.materializationConfigs.canvas?.plotBorder;
|
|
271
|
+
if (isPlainObject(border) && Object.hasOwn(border, "underlay") && border.underlay !== true) {
|
|
272
|
+
throw new Error("Stored plot border underlay must be canonical true intent.");
|
|
273
|
+
}
|
|
202
274
|
for (const id of Object.keys(program.guideConfigs.axis ?? {})) {
|
|
275
|
+
const config = program.guideConfigs.axis[id];
|
|
276
|
+
if (config?.frame !== undefined && Object.hasOwn(config.frame, "underlay") &&
|
|
277
|
+
!cartesianOwners.some(owner => owner.id === id)) {
|
|
278
|
+
throw new Error("Stored axis underlay requires a Cartesian axis owner.");
|
|
279
|
+
}
|
|
203
280
|
if (!AXIS_RESOURCE_CHANNELS.includes(id) && !Object.hasOwn(program.semanticSpec.guides.axis ?? {}, id)) {
|
|
204
281
|
throw invalidSnapshot("Named axis configs require their semantic owner.", path);
|
|
205
282
|
}
|
|
@@ -242,6 +319,7 @@ export function validateProgramState(program, path) {
|
|
|
242
319
|
if (program.guideConfigs.legendGroups !== undefined) validateLegendGroupState(program.guideConfigs.legendGroups);
|
|
243
320
|
for (const [kind, config] of Object.entries(program.guideConfigs.legend ?? {})) {
|
|
244
321
|
validateLegendClip(kind,config);
|
|
322
|
+
validateLegendSpacing(kind,config);
|
|
245
323
|
if (localLegendPosition(config) !== undefined) {
|
|
246
324
|
const descriptor = describeLegendBlock(program, kind, config);
|
|
247
325
|
for (const [key, override] of Object.entries(config.blockOverrides)) {
|
|
@@ -301,22 +379,17 @@ export function validateProgramState(program, path) {
|
|
|
301
379
|
if (["gradient", "strokeGradient"].includes(kind) && config.representation !== "symbols") {
|
|
302
380
|
validateLegendGradient(config.gradient, "Stored legend gradient");
|
|
303
381
|
}
|
|
382
|
+
if (["gradient", "strokeGradient"].includes(kind) && config.representation !== "symbols" && config.sampling !== undefined) {
|
|
383
|
+
validateStoredColorLegendSampling(program, config, path);
|
|
384
|
+
if (config.sampling.mode !== "values" || config.count !== config.sampling.count) {
|
|
385
|
+
throw invalidSnapshot("Gradient exact sampling must retain its automatic count.", path);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
304
388
|
if (config.representation === undefined) continue;
|
|
305
389
|
if (kind !== "gradient" || !["symbols", "gradient"].includes(config.representation)) throw invalidSnapshot("Invalid continuous color legend representation.", path);
|
|
306
390
|
if (config.representation === "symbols") {
|
|
307
391
|
if (config.gradient !== undefined) throw invalidSnapshot("Symbol color legend cannot store gradient dimensions.", path);
|
|
308
|
-
|
|
309
|
-
const sample = config.sampling;
|
|
310
|
-
exactKeys(sample, sample.mode === "values" ? ["mode", "values", "count"] : ["mode", "count"], path);
|
|
311
|
-
if (!["auto", "values"].includes(sample.mode)) throw invalidSnapshot("Invalid color legend sample mode.", path);
|
|
312
|
-
if (!Number.isInteger(sample.count) || sample.count < 2) throw invalidSnapshot("Invalid color legend sample count.", path);
|
|
313
|
-
validateGeneratedItemLimit(sample.count, "Color legend sample count");
|
|
314
|
-
const scale = program.resolvedScales[config.scale];
|
|
315
|
-
if (!isContinuousColorScaleType(scale?.type)) throw invalidSnapshot("Sampled color legend requires a continuous color scale.", path);
|
|
316
|
-
if (sample.mode === "values") {
|
|
317
|
-
requireArray(sample.values, path);
|
|
318
|
-
if (!sample.values.length || sample.values.length > 100 || sample.values.some((value, index) => !Number.isFinite(value) || index > 0 && value <= sample.values[index - 1] || value < Math.min(...scale.domain) || value > Math.max(...scale.domain) || scale.type === "log" && value <= 0)) throw invalidSnapshot("Invalid exact color legend samples.", path);
|
|
319
|
-
}
|
|
392
|
+
validateStoredColorLegendSampling(program, config, path);
|
|
320
393
|
requireObject(config.symbol, path);
|
|
321
394
|
validateKeys(config.symbol, ["type", "radius", "stroke", "strokeWidth"], "Stored sampled color symbol");
|
|
322
395
|
if (config.symbol.type !== "point") throw invalidSnapshot("Invalid sampled color symbol type.", path);
|
|
@@ -326,7 +399,7 @@ export function validateProgramState(program, path) {
|
|
|
326
399
|
if (!["left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right"].includes(config.position) || !["horizontal", "vertical"].includes(config.direction) || config.layout !== "edge" || !["left", "center", "right"].includes(config.align) || !["top", "left"].includes(config.titlePosition) || config.columns !== undefined && (!Number.isInteger(config.columns) || config.columns < 1)) throw invalidSnapshot("Invalid sampled color item layout.", path);
|
|
327
400
|
validatePositiveFinite(config.itemGap, "Stored sampled color itemGap");
|
|
328
401
|
validateNonNegativeFinite(config.offset, "Stored sampled color offset");
|
|
329
|
-
} else if (["
|
|
402
|
+
} else if (["symbol", "columns", "direction", "layout"].some(key => config[key] !== undefined)) throw invalidSnapshot("Gradient color legend cannot store symbol representation settings.", path);
|
|
330
403
|
}
|
|
331
404
|
|
|
332
405
|
const regressionFits = program.materializationConfigs.regressionFits ?? {};
|
|
@@ -336,6 +409,18 @@ export function validateProgramState(program, path) {
|
|
|
336
409
|
validateRegressionFitStatistics(cache, dataset, resources.get("data").get(dataset?.source));
|
|
337
410
|
}
|
|
338
411
|
for (const dataset of semantic.datasets) {
|
|
412
|
+
if(dataset.transform?.[0]?.type==="lookup"){
|
|
413
|
+
orderedDatasetAncestors(semantic.datasets,dataset.id);
|
|
414
|
+
const transform=dataset.transform[0],source=resources.get("data").get(dataset.source),right=resources.get("data").get(transform.lookup.data);
|
|
415
|
+
if(source===undefined||right===undefined)throw invalidSnapshot("Lookup requires both existing input datasets.",path);
|
|
416
|
+
const missingSchemas = [source, right, dataset].some(input => input.schema === undefined);
|
|
417
|
+
if (!allowMissingLookupSchemas || !missingSchemas) {
|
|
418
|
+
const schema=deriveTransformSchema(source.schema,transform,dataset.values,
|
|
419
|
+
datasetSchemaContext(source,transform,dataset.id,id=>resources.get("data").get(id)));
|
|
420
|
+
if(!sameLookupValue(dataset.schema,schema))throw invalidSnapshot("Lookup schema does not match both inputs.",path);
|
|
421
|
+
}
|
|
422
|
+
if(dataset.values!==undefined&&!sameLookupValue(dataset.values,deriveLookupRows(source.values,right.values,transform)))throw invalidSnapshot("Lookup values do not match both input datasets.",path);
|
|
423
|
+
}
|
|
339
424
|
if (dataset.transform?.[0]?.type !== "regressionStatistics") continue;
|
|
340
425
|
const fit = resources.get("data").get(dataset.source);
|
|
341
426
|
requireRegressionStatisticsSource(fit);
|
|
@@ -378,7 +463,12 @@ export function validateProgramState(program, path) {
|
|
|
378
463
|
}
|
|
379
464
|
}
|
|
380
465
|
for (const id of Object.keys(program.markConfigs)) {
|
|
466
|
+
requestedMarkClip(program.markConfigs[id]);
|
|
467
|
+
validateMarkClipCoordinate(program, resources.get("mark").get(id), program.markConfigs[id].clip);
|
|
381
468
|
if (!resources.get("mark").has(id)) throw invalidSnapshot(`Mark config "${id}" has no layer owner.`, path);
|
|
469
|
+
if (resources.get("mark").get(id).mark.type === "point" && program.markConfigs[id].shape !== undefined) {
|
|
470
|
+
validatePointShape(program.markConfigs[id].shape);
|
|
471
|
+
}
|
|
382
472
|
if (resources.get("mark").get(id).mark.type === "area") {
|
|
383
473
|
validateAreaPaintConfig(program.markConfigs[id]);
|
|
384
474
|
}
|
package/src/persistence.js
CHANGED
|
@@ -7,6 +7,7 @@ import { decodeValue, encodeValue, encodeSharedValue, invalidSnapshot } from "./
|
|
|
7
7
|
import { exactKeys, requireObject, STATE_KEYS, validateGraphic, validateProgramState, validateTrace } from "./persistence/validation.js";
|
|
8
8
|
import {
|
|
9
9
|
deriveTransformSchema,
|
|
10
|
+
datasetSchemaContext,
|
|
10
11
|
inferDatasetSchema,
|
|
11
12
|
validateDatasetSchema
|
|
12
13
|
} from "./grammar/datasetSchema.js";
|
|
@@ -63,7 +64,7 @@ function restoreState(state, extensions, path = "payload", { requireSchemas = fa
|
|
|
63
64
|
{ requireSchemas }
|
|
64
65
|
)]));
|
|
65
66
|
const program = new ChartProgram({ ...state, children });
|
|
66
|
-
validateProgramState(program, path);
|
|
67
|
+
validateProgramState(program, path, { allowMissingLookupSchemas: !requireSchemas });
|
|
67
68
|
if (requireSchemas) {
|
|
68
69
|
for (const dataset of program.semanticSpec.datasets) {
|
|
69
70
|
if (dataset.schema === undefined) {
|
|
@@ -96,7 +97,8 @@ function migrateStateSchemas(state) {
|
|
|
96
97
|
sourceSchema,
|
|
97
98
|
dataset.transform?.[0] ?? {},
|
|
98
99
|
dataset.values,
|
|
99
|
-
{
|
|
100
|
+
datasetSchemaContext({...byId.get(dataset.source),schema:sourceSchema},dataset.transform?.[0]??{},dataset.id,
|
|
101
|
+
input=>({...byId.get(input),schema:schemaFor(input)}))
|
|
100
102
|
);
|
|
101
103
|
}
|
|
102
104
|
active.delete(id);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MAX_RASTER_DIMENSION, MAX_RASTER_PIXELS } from "./limits.js";
|
|
2
|
+
import { validateGraphicClip } from "../../grammar/schemas/graphicClip.js";
|
|
2
3
|
import { prepareIsolatedGlyphs, drawIsolatedGlyph } from "./isolated.js";
|
|
3
4
|
import { resolveCanvasSolidPaint } from "./fill.js";
|
|
4
5
|
import { annotateError } from "../../core/diagnostics.js";
|
|
@@ -158,6 +159,16 @@ export function drawResolvedGraphicSpec(target, context, options = {}) {
|
|
|
158
159
|
enterNestedCanvas(context, id, object);
|
|
159
160
|
return;
|
|
160
161
|
}
|
|
162
|
+
if (object.clip !== undefined) {
|
|
163
|
+
validateGraphicClip(object.clip, object.type);
|
|
164
|
+
requireContextMethod(context, "rect", `Graphic "${id}" clip`);
|
|
165
|
+
requireContextMethod(context, "clip", `Graphic "${id}" clip`);
|
|
166
|
+
context.save();
|
|
167
|
+
context.beginPath();
|
|
168
|
+
const {x, y, width, height} = object.clip;
|
|
169
|
+
context.rect(x, y, width, height);
|
|
170
|
+
context.clip();
|
|
171
|
+
}
|
|
161
172
|
if (object.type === "collection") {
|
|
162
173
|
context.save();
|
|
163
174
|
return;
|
|
@@ -179,6 +190,7 @@ export function drawResolvedGraphicSpec(target, context, options = {}) {
|
|
|
179
190
|
) {
|
|
180
191
|
context.restore();
|
|
181
192
|
}
|
|
193
|
+
if (object.clip !== undefined) context.restore();
|
|
182
194
|
}
|
|
183
195
|
});
|
|
184
196
|
}
|
|
@@ -221,6 +233,10 @@ export function renderCanvasProgram(program, context, { pixelRatio = 1 } = {}, c
|
|
|
221
233
|
), { code: "resource-limit", operation: "render", optionPath: "physicalPixels", limit: MAX_RASTER_PIXELS, actual: physicalWidth * physicalHeight });
|
|
222
234
|
}
|
|
223
235
|
preflightCanvasGraphicSpec(target, pixelRatio);
|
|
236
|
+
if (Object.values(graphicSpec.objects).some(object => object.clip !== undefined)) {
|
|
237
|
+
requireContextMethod(context, "rect", "Graphic clip");
|
|
238
|
+
requireContextMethod(context, "clip", "Graphic clip");
|
|
239
|
+
}
|
|
224
240
|
const isolated = prepareIsolatedGlyphs(target, context, {pixelRatio, createCanvas});
|
|
225
241
|
|
|
226
242
|
context.canvas.width = physicalWidth;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolvePrimitiveGraphicBounds } from "../../grammar/schemas/graphicBounds.js";
|
|
2
|
+
import { graphicClipBounds, intersectClipBounds, validateGraphicClip } from "../../grammar/schemas/graphicClip.js";
|
|
2
3
|
import { walkGraphicTreeEvents } from "../../grammar/schemas/graphicTree.js";
|
|
3
4
|
import { MAX_RASTER_DIMENSION, MAX_RASTER_PIXELS } from "./limits.js";
|
|
4
5
|
|
|
@@ -124,10 +125,19 @@ export function prepareIsolatedGlyphs(target, context, { pixelRatio = 1, createC
|
|
|
124
125
|
};
|
|
125
126
|
frames.push({offset, clip});
|
|
126
127
|
}
|
|
128
|
+
if (object.clip !== undefined) {
|
|
129
|
+
const parent = frames.at(-1);
|
|
130
|
+
validateGraphicClip(object.clip, object.type);
|
|
131
|
+
frames.push({offset: parent.offset, clip: intersectClipBounds(parent.clip,
|
|
132
|
+
graphicClipBounds(object.clip, ...parent.offset))});
|
|
133
|
+
}
|
|
127
134
|
if (!object.items) plan(id, object, object.type);
|
|
128
135
|
},
|
|
129
136
|
item({id, object, owner}) { plan(id, object, object.type ?? owner.type); },
|
|
130
|
-
exit({object}) {
|
|
137
|
+
exit({object}) {
|
|
138
|
+
if (object.clip !== undefined) frames.pop();
|
|
139
|
+
if (object.type === "canvas") frames.pop();
|
|
140
|
+
}
|
|
131
141
|
});
|
|
132
142
|
return {plans, surface, pixelRatio};
|
|
133
143
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveLinearGradientCoordinates } from "../../grammar/paint.js";
|
|
2
|
+
import { validateGraphicClip } from "../../grammar/schemas/graphicClip.js";
|
|
2
3
|
import { resolvePathCommandBounds } from
|
|
3
4
|
"../../grammar/schemas/graphicBounds.js";
|
|
4
5
|
import { walkGraphicTreeEvents } from
|
|
@@ -143,6 +144,12 @@ export function preflightCanvasGraphicSpec(target, scale = 1) {
|
|
|
143
144
|
offset = id === target.canvasId ? undefined : parent;
|
|
144
145
|
}
|
|
145
146
|
preflightGraphic(id, object, scale, object.type, offset);
|
|
147
|
+
if (object.clip !== undefined) {
|
|
148
|
+
const clip = validateGraphicClip(object.clip, object.type);
|
|
149
|
+
requireProperties(clip, ["x", "y", "width", "height"], `Graphic "${id}" Canvas clip`, scale, offset);
|
|
150
|
+
requireCanvasNativeValue(clip.x + clip.width, `Graphic "${id}" Canvas clip`, scale, offset?.[0]);
|
|
151
|
+
requireCanvasNativeValue(clip.y + clip.height, `Graphic "${id}" Canvas clip`, scale, offset?.[1]);
|
|
152
|
+
}
|
|
146
153
|
},
|
|
147
154
|
item({ id, object, owner }) {
|
|
148
155
|
preflightGraphic(
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { isOwned } from '../../core/immutable.js';
|
|
2
|
+
import { render } from './index.js';
|
|
3
|
+
|
|
4
|
+
const compiled = new WeakMap();
|
|
5
|
+
// Translate only concrete position properties. Path/text child coordinates keep
|
|
6
|
+
// their authored relationship; gradients retain their local bounding-box units.
|
|
7
|
+
function position(properties, dx, dy) {
|
|
8
|
+
if (!dx && !dy) return properties;
|
|
9
|
+
const result = { ...properties };
|
|
10
|
+
for (const key of ['x', 'x1', 'x2']) if (key in result) result[key] += dx;
|
|
11
|
+
for (const key of ['y', 'y1', 'y2']) if (key in result) result[key] += dy;
|
|
12
|
+
if (result.commands) result.commands = result.commands.map(command => position(command, dx, dy));
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
function compile(scene) {
|
|
16
|
+
if (scene?.version !== 2 || ![scene.width, scene.height].every(n => Number.isFinite(n) && n > 0) || !Array.isArray(scene.nodes)) {
|
|
17
|
+
throw new TypeError('renderTransitionScene requires a version 2 Canvas scene.');
|
|
18
|
+
}
|
|
19
|
+
const objects = { canvas: { type: 'canvas', properties: { width: scene.width, height: scene.height }, children: [] } };
|
|
20
|
+
const keys = new Set();
|
|
21
|
+
let index = 0, activeClips = [];
|
|
22
|
+
const groups = [objects.canvas], origins = [[0, 0]];
|
|
23
|
+
const validClip = clip => Array.isArray(clip) && clip.length === 4 &&
|
|
24
|
+
clip.every(Number.isFinite) && clip[2] >= 0 && clip[3] >= 0;
|
|
25
|
+
for (const node of scene.nodes) {
|
|
26
|
+
const clips = node.clips ?? [node.clip];
|
|
27
|
+
if (typeof node.key !== 'string' || keys.has(node.key) || !['circle','rect','line','path','text'].includes(node.type) ||
|
|
28
|
+
node.offset?.length !== 2 || !node.offset.every(Number.isFinite) || !validClip(node.clip) ||
|
|
29
|
+
!Array.isArray(clips) || !clips.every(validClip) || !node.properties || typeof node.properties !== 'object') {
|
|
30
|
+
throw new TypeError('Canvas scene nodes require unique keys, concrete properties, offsets and clips.');
|
|
31
|
+
}
|
|
32
|
+
keys.add(node.key);
|
|
33
|
+
let common = 0;
|
|
34
|
+
while (common < Math.min(clips.length, activeClips.length) &&
|
|
35
|
+
clips[common].every((value, i) => value === activeClips[common][i])) common++;
|
|
36
|
+
groups.length = common + 1; origins.length = common + 1;
|
|
37
|
+
// Retain shared concrete clip ancestors across adjacent nodes. Rebuilding
|
|
38
|
+
// a fractional ancestor between its children can change raster coverage.
|
|
39
|
+
for (let i = common; i < clips.length; i++) {
|
|
40
|
+
const clip = clips[i], origin = origins.at(-1), parent = groups.at(-1);
|
|
41
|
+
const id = `clip-${index++}`;
|
|
42
|
+
const nested = objects[id] = { type: 'canvas', properties: {
|
|
43
|
+
x: clip[0] - origin[0], y: clip[1] - origin[1], width: clip[2], height: clip[3]
|
|
44
|
+
}, children: [] };
|
|
45
|
+
parent.children.push(id); groups.push(nested); origins.push([clip[0], clip[1]]);
|
|
46
|
+
}
|
|
47
|
+
activeClips = clips;
|
|
48
|
+
const group = groups.at(-1), origin = origins.at(-1);
|
|
49
|
+
const id = `node-${index++}`;
|
|
50
|
+
objects[id] = { type: node.type, properties: position(node.properties,
|
|
51
|
+
node.offset[0] - origin[0], node.offset[1] - origin[1]) };
|
|
52
|
+
group.children.push(id);
|
|
53
|
+
}
|
|
54
|
+
return { graphicSpec: { objects, order: ['canvas'] } };
|
|
55
|
+
}
|
|
56
|
+
/** Paint concrete Canvas scenes with the ordinary renderer; no semantics or DOM. */
|
|
57
|
+
export function renderTransitionScene(scene, context, options) {
|
|
58
|
+
// Only immutable producer scenes are cacheable. Intermediate animation frames
|
|
59
|
+
// may be reused/mutated by a caller and must always reflect their current data.
|
|
60
|
+
let program = isOwned(scene) ? compiled.get(scene) : undefined;
|
|
61
|
+
if (!program) {
|
|
62
|
+
program = compile(scene);
|
|
63
|
+
if (isOwned(scene)) compiled.set(scene, program);
|
|
64
|
+
}
|
|
65
|
+
return render(program, context, options);
|
|
66
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveCanvasSolidPaint } from "./fill.js";
|
|
2
2
|
import { requireFiniteProperty, requireStringProperty } from "./validation.js";
|
|
3
|
-
import { validateConcreteGraphicProperties } from
|
|
3
|
+
import { validateConcreteGraphicProperties, validateConcreteGraphicValue } from
|
|
4
4
|
"../../grammar/schemas/concreteGraphic.js";
|
|
5
5
|
import { normalizeRendererFontWeight } from "../text.js";
|
|
6
6
|
|
|
@@ -13,7 +13,8 @@ function drawText(context, child, collectionId) {
|
|
|
13
13
|
const fontSize = requireFiniteProperty(properties, "fontSize", graphicId);
|
|
14
14
|
const rotation = properties.rotation ?? 0;
|
|
15
15
|
const opacity = properties.opacity ?? 1;
|
|
16
|
-
const text =
|
|
16
|
+
const text = properties.text;
|
|
17
|
+
validateConcreteGraphicValue("text", "text", text);
|
|
17
18
|
const fill = requireStringProperty(properties, "fill", graphicId);
|
|
18
19
|
const fontFamily = requireStringProperty(
|
|
19
20
|
properties,
|