ggaction 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/package.json +84 -0
- package/src/AGENTS.md +119 -0
- package/src/ChartProgram.js +10 -0
- package/src/actions/boxPlots/components.js +88 -0
- package/src/actions/boxPlots/create.js +84 -0
- package/src/actions/boxPlots/index.js +10 -0
- package/src/actions/boxPlots/materialize.js +183 -0
- package/src/actions/boxPlots/options.js +147 -0
- package/src/actions/boxPlots/resolve.js +38 -0
- package/src/actions/canvas/actions.js +144 -0
- package/src/actions/canvas/index.js +6 -0
- package/src/actions/coordinates/actions.js +85 -0
- package/src/actions/coordinates/index.js +5 -0
- package/src/actions/data/box.js +40 -0
- package/src/actions/data/create.js +33 -0
- package/src/actions/data/density.js +62 -0
- package/src/actions/data/derived.js +52 -0
- package/src/actions/data/filter.js +168 -0
- package/src/actions/data/index.js +32 -0
- package/src/actions/data/interval.js +69 -0
- package/src/actions/data/regression.js +71 -0
- package/src/actions/data/shared.js +25 -0
- package/src/actions/encodings/appearance.js +243 -0
- package/src/actions/encodings/barWidth.js +60 -0
- package/src/actions/encodings/color.js +329 -0
- package/src/actions/encodings/density.js +225 -0
- package/src/actions/encodings/histogram.js +50 -0
- package/src/actions/encodings/index.js +23 -0
- package/src/actions/encodings/offset.js +104 -0
- package/src/actions/encodings/position/policies/area.js +24 -0
- package/src/actions/encodings/position/policies/bar.js +110 -0
- package/src/actions/encodings/position/policies/common.js +17 -0
- package/src/actions/encodings/position/policies/index.js +25 -0
- package/src/actions/encodings/position/policies/line.js +74 -0
- package/src/actions/encodings/position/policies/point.js +19 -0
- package/src/actions/encodings/position/policies/rule.js +17 -0
- package/src/actions/encodings/position/resolve.js +170 -0
- package/src/actions/encodings/position.js +176 -0
- package/src/actions/encodings/ranged.js +276 -0
- package/src/actions/encodings/ruleAppearance.js +47 -0
- package/src/actions/encodings/shared.js +164 -0
- package/src/actions/encodings/strokeDash.js +145 -0
- package/src/actions/errorBands/components.js +81 -0
- package/src/actions/errorBands/create.js +159 -0
- package/src/actions/errorBands/index.js +7 -0
- package/src/actions/errorBands/resolve.js +28 -0
- package/src/actions/errorBars/components.js +62 -0
- package/src/actions/errorBars/create.js +144 -0
- package/src/actions/errorBars/index.js +7 -0
- package/src/actions/errorBars/resolve.js +25 -0
- package/src/actions/guides/axes/axes.js +204 -0
- package/src/actions/guides/axes/axis.js +131 -0
- package/src/actions/guides/axes/index.js +17 -0
- package/src/actions/guides/axes/labels.js +231 -0
- package/src/actions/guides/axes/lines.js +149 -0
- package/src/actions/guides/axes/policy.js +155 -0
- package/src/actions/guides/axes/tickGroups.js +149 -0
- package/src/actions/guides/axes/ticks.js +179 -0
- package/src/actions/guides/axes/titles.js +264 -0
- package/src/actions/guides/grids/grid.js +228 -0
- package/src/actions/guides/grids/index.js +1 -0
- package/src/actions/guides/grids/resolve.js +258 -0
- package/src/actions/guides/guides.js +138 -0
- package/src/actions/guides/index.js +11 -0
- package/src/actions/guides/legends/categorical/actions.js +329 -0
- package/src/actions/guides/legends/categorical/components.js +140 -0
- package/src/actions/guides/legends/categorical/index.js +49 -0
- package/src/actions/guides/legends/categorical/layout.js +445 -0
- package/src/actions/guides/legends/categorical/options.js +188 -0
- package/src/actions/guides/legends/categorical/recipes.js +120 -0
- package/src/actions/guides/legends/categorical/resolve.js +131 -0
- package/src/actions/guides/legends/categorical/symbols.js +280 -0
- package/src/actions/guides/legends/categorical/validation.js +27 -0
- package/src/actions/guides/legends/continuous/common.js +273 -0
- package/src/actions/guides/legends/continuous/gradient.js +363 -0
- package/src/actions/guides/legends/continuous/index.js +25 -0
- package/src/actions/guides/legends/continuous/interval.js +234 -0
- package/src/actions/guides/legends/continuous/opacity.js +382 -0
- package/src/actions/guides/legends/edit.js +314 -0
- package/src/actions/guides/legends/index.js +9 -0
- package/src/actions/guides/legends/size.js +220 -0
- package/src/actions/guides/tickValues.js +84 -0
- package/src/actions/index.js +31 -0
- package/src/actions/intervals/resolve.js +352 -0
- package/src/actions/marks/area.js +369 -0
- package/src/actions/marks/bar/create.js +46 -0
- package/src/actions/marks/bar/edit.js +90 -0
- package/src/actions/marks/bar/index.js +9 -0
- package/src/actions/marks/bar/materialize.js +130 -0
- package/src/actions/marks/index.js +13 -0
- package/src/actions/marks/line.js +248 -0
- package/src/actions/marks/point.js +420 -0
- package/src/actions/marks/rule.js +287 -0
- package/src/actions/marks/shared.js +112 -0
- package/src/actions/primitives/createGraphics.js +163 -0
- package/src/actions/primitives/editGraphics.js +208 -0
- package/src/actions/primitives/index.js +9 -0
- package/src/actions/primitives/semantic.js +168 -0
- package/src/actions/primitives/semanticValue.js +182 -0
- package/src/actions/regression/components.js +216 -0
- package/src/actions/regression/create.js +102 -0
- package/src/actions/regression/index.js +23 -0
- package/src/actions/regression/resolve.js +57 -0
- package/src/actions/scales/consumers.js +283 -0
- package/src/actions/scales/create.js +148 -0
- package/src/actions/scales/definitions.js +319 -0
- package/src/actions/scales/edit.js +290 -0
- package/src/actions/scales/index.js +11 -0
- package/src/actions/scales/materialize.js +466 -0
- package/src/actions/scales/quantitativeColor.js +45 -0
- package/src/actions/selection/actions.js +384 -0
- package/src/actions/selection/index.js +23 -0
- package/src/actions/titles/actions.js +235 -0
- package/src/actions/titles/index.js +19 -0
- package/src/actions/titles/resolve.js +321 -0
- package/src/core/ChartProgram.js +316 -0
- package/src/core/action.js +120 -0
- package/src/core/identifiers.js +28 -0
- package/src/core/immutable.js +94 -0
- package/src/core/specs.js +29 -0
- package/src/core/textMetrics.js +23 -0
- package/src/core/validation.js +54 -0
- package/src/core/vocabulary.js +58 -0
- package/src/extension.js +2 -0
- package/src/grammar/aggregate.js +259 -0
- package/src/grammar/areaSeries.js +217 -0
- package/src/grammar/bars/aggregate.js +98 -0
- package/src/grammar/bars/geometry.js +54 -0
- package/src/grammar/bars/policy.js +90 -0
- package/src/grammar/boxPlot.js +140 -0
- package/src/grammar/coordinates.js +25 -0
- package/src/grammar/curveCommands.js +356 -0
- package/src/grammar/density.js +296 -0
- package/src/grammar/filter.js +169 -0
- package/src/grammar/histogram.js +332 -0
- package/src/grammar/interval.js +343 -0
- package/src/grammar/lineSeries.js +197 -0
- package/src/grammar/markFilter.js +46 -0
- package/src/grammar/markSelection.js +326 -0
- package/src/grammar/palettes.js +208 -0
- package/src/grammar/pathCommands.js +86 -0
- package/src/grammar/pointShapes.js +164 -0
- package/src/grammar/positionCompatibility.js +32 -0
- package/src/grammar/regression.js +578 -0
- package/src/grammar/ruleAppearance.js +12 -0
- package/src/grammar/rules.js +90 -0
- package/src/grammar/scales/appearance.js +185 -0
- package/src/grammar/scales/color.js +349 -0
- package/src/grammar/scales/continuous.js +111 -0
- package/src/grammar/scales/definition.js +153 -0
- package/src/grammar/scales/discretized.js +159 -0
- package/src/grammar/scales/fields.js +133 -0
- package/src/grammar/scales/index.js +12 -0
- package/src/grammar/scales/mapping.js +27 -0
- package/src/grammar/scales/ordinal.js +221 -0
- package/src/grammar/scales/policies.js +50 -0
- package/src/grammar/scales/temporal.js +53 -0
- package/src/grammar/scales/transformed.js +236 -0
- package/src/grammar/scales/types.js +102 -0
- package/src/grammar/scales/validation.js +66 -0
- package/src/grammar/scales.js +1 -0
- package/src/grammar/schemas/concreteGraphic.js +73 -0
- package/src/grammar/schemas/graphic.js +131 -0
- package/src/grammar/schemas/graphicBounds.js +146 -0
- package/src/grammar/schemas/graphicTree.js +133 -0
- package/src/grammar/schemas/mark.js +4 -0
- package/src/grammar/schemas/semanticPath.js +174 -0
- package/src/grammar/seriesLayout.js +76 -0
- package/src/grammar/ticks.js +149 -0
- package/src/grammar/transforms.js +37 -0
- package/src/index.js +2 -0
- package/src/layout/canvas.js +114 -0
- package/src/layout/grid.js +17 -0
- package/src/layout/legend.js +51 -0
- package/src/layout/text.js +62 -0
- package/src/layout/title.js +107 -0
- package/src/materialization/bars/aggregate.js +184 -0
- package/src/materialization/bars/grouped.js +117 -0
- package/src/materialization/bars/histogram.js +163 -0
- package/src/materialization/bars/ranged.js +53 -0
- package/src/materialization/bars/resolve.js +56 -0
- package/src/materialization/dataProvenance.js +15 -0
- package/src/materialization/density.js +29 -0
- package/src/materialization/dependencies.js +134 -0
- package/src/materialization/encodings.js +50 -0
- package/src/materialization/graphicHierarchy.js +104 -0
- package/src/materialization/legends.js +20 -0
- package/src/materialization/marks.js +110 -0
- package/src/materialization/planner.js +38 -0
- package/src/materialization/selection/filter.js +40 -0
- package/src/materialization/selection/items.js +503 -0
- package/src/materialization/selection/path.js +37 -0
- package/src/materialization/selection/point.js +118 -0
- package/src/materialization/selection/policies/area.js +10 -0
- package/src/materialization/selection/policies/bar.js +10 -0
- package/src/materialization/selection/policies/index.js +40 -0
- package/src/materialization/selection/policies/line.js +10 -0
- package/src/materialization/selection/policies/point.js +10 -0
- package/src/materialization/selection/policies/rule.js +10 -0
- package/src/materialization/selection/state.js +67 -0
- package/src/materialization/selection/styles.js +183 -0
- package/src/renderers/canvas/circle.js +62 -0
- package/src/renderers/canvas/index.js +132 -0
- package/src/renderers/canvas/line.js +54 -0
- package/src/renderers/canvas/path.js +98 -0
- package/src/renderers/canvas/rect.js +57 -0
- package/src/renderers/canvas/text.js +96 -0
- package/src/renderers/canvas/validation.js +23 -0
- package/src/renderers/png.js +29 -0
- package/src/selectors/coordinates.js +13 -0
- package/src/selectors/datasets.js +13 -0
- package/src/selectors/index.js +4 -0
- package/src/selectors/layers.js +32 -0
- package/src/selectors/scales.js +21 -0
- package/src/theme/defaults.js +13 -0
- package/types/extension.d.ts +14 -0
- package/types/index.d.ts +81 -0
- package/types/png.d.ts +14 -0
- package/types/program.d.ts +1096 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getMarkMaterializationStep } from "./marks.js";
|
|
2
|
+
import { requireLayer } from "../selectors/layers.js";
|
|
3
|
+
import { buildMaterializationPlan } from "./planner.js";
|
|
4
|
+
|
|
5
|
+
export function planDensityRematerialization(program, target) {
|
|
6
|
+
const layer = requireLayer(
|
|
7
|
+
program,
|
|
8
|
+
target,
|
|
9
|
+
`Unknown density materialization target "${target}"`
|
|
10
|
+
);
|
|
11
|
+
const scaleIds = new Set([
|
|
12
|
+
layer.encoding?.x?.scale,
|
|
13
|
+
layer.encoding?.y?.scale
|
|
14
|
+
].filter(Boolean));
|
|
15
|
+
const orderedLayers = [
|
|
16
|
+
layer,
|
|
17
|
+
...program.semanticSpec.layers.filter(candidate => candidate.id !== target)
|
|
18
|
+
];
|
|
19
|
+
const plan = [];
|
|
20
|
+
for (const candidate of orderedLayers) {
|
|
21
|
+
const usesAffectedScale = Object.values(candidate.encoding ?? {}).some(
|
|
22
|
+
encoding => scaleIds.has(encoding?.scale)
|
|
23
|
+
);
|
|
24
|
+
if (!usesAffectedScale) continue;
|
|
25
|
+
const step = getMarkMaterializationStep(program, candidate);
|
|
26
|
+
if (step !== undefined) plan.push(step);
|
|
27
|
+
}
|
|
28
|
+
return buildMaterializationPlan({ marks: plan });
|
|
29
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { getMarkMaterializationStep } from "./marks.js";
|
|
2
|
+
import { requireLayer } from "../selectors/layers.js";
|
|
3
|
+
import {
|
|
4
|
+
applyMaterializationPlan,
|
|
5
|
+
buildMaterializationPlan
|
|
6
|
+
} from "./planner.js";
|
|
7
|
+
import {
|
|
8
|
+
hasMaterializedLegend,
|
|
9
|
+
materializedLegendUsesScale
|
|
10
|
+
} from "./legends.js";
|
|
11
|
+
|
|
12
|
+
function usesPositionalScale(program, id) {
|
|
13
|
+
return program.semanticSpec.layers.some(layer =>
|
|
14
|
+
["x", "y", "x2", "y2", "xOffset"].some(
|
|
15
|
+
channel => layer.encoding?.[channel]?.scale === id
|
|
16
|
+
)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function needsCanvasScaleRematerialization(program, scale) {
|
|
21
|
+
return (
|
|
22
|
+
(scale.range === "auto" ||
|
|
23
|
+
program.semanticSpec.guides.axis?.x?.scale === scale.id ||
|
|
24
|
+
program.semanticSpec.guides.axis?.y?.scale === scale.id ||
|
|
25
|
+
program.semanticSpec.guides.grid?.horizontal?.scale === scale.id ||
|
|
26
|
+
program.semanticSpec.guides.grid?.vertical?.scale === scale.id) &&
|
|
27
|
+
program.resolvedScales[scale.id] !== undefined &&
|
|
28
|
+
usesPositionalScale(program, scale.id)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function hasTitle(program) {
|
|
33
|
+
return (
|
|
34
|
+
program.semanticSpec.title.text !== undefined &&
|
|
35
|
+
program.titleConfig !== undefined
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function planCanvasRematerialization(program) {
|
|
40
|
+
const scales = [];
|
|
41
|
+
for (const scale of program.semanticSpec.scales) {
|
|
42
|
+
if (needsCanvasScaleRematerialization(program, scale)) {
|
|
43
|
+
scales.push({
|
|
44
|
+
op: "rematerializeScale",
|
|
45
|
+
args: { id: scale.id, guides: false }
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const marks = [];
|
|
50
|
+
for (const layer of program.semanticSpec.layers) {
|
|
51
|
+
const step = getMarkMaterializationStep(program, layer);
|
|
52
|
+
if (step !== undefined) marks.push(step);
|
|
53
|
+
}
|
|
54
|
+
const guides = program.semanticSpec.scales.flatMap(scale =>
|
|
55
|
+
needsCanvasScaleRematerialization(program, scale)
|
|
56
|
+
? planScaleGuideRematerialization(program, scale.id)
|
|
57
|
+
: []
|
|
58
|
+
);
|
|
59
|
+
if (hasMaterializedLegend(program)) {
|
|
60
|
+
guides.push({ op: "rematerializeLegend" });
|
|
61
|
+
}
|
|
62
|
+
const layout = hasTitle(program) ? [{ op: "rematerializeTitle" }] : [];
|
|
63
|
+
return buildMaterializationPlan({ scales, marks, guides, layout });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function planScaleGuideRematerialization(program, id) {
|
|
67
|
+
const guides = [];
|
|
68
|
+
const objects = program.graphicSpec?.objects ?? {};
|
|
69
|
+
const guideConfigs = program.guideConfigs ?? {};
|
|
70
|
+
if (
|
|
71
|
+
objects.xAxisLine &&
|
|
72
|
+
program.semanticSpec.guides.axis?.x?.scale === id
|
|
73
|
+
) {
|
|
74
|
+
guides.push({ op: "editXAxisLine" });
|
|
75
|
+
}
|
|
76
|
+
if (
|
|
77
|
+
objects.yAxisLine &&
|
|
78
|
+
program.semanticSpec.guides.axis?.y?.scale === id
|
|
79
|
+
) {
|
|
80
|
+
guides.push({ op: "editYAxisLine" });
|
|
81
|
+
}
|
|
82
|
+
for (const channel of ["x", "y"]) {
|
|
83
|
+
for (const component of ["ticks", "labels", "title"]) {
|
|
84
|
+
if (guideConfigs.axis?.[channel]?.[component]?.scale === id) {
|
|
85
|
+
const suffix = component[0].toUpperCase() + component.slice(1);
|
|
86
|
+
guides.push({ op: `edit${channel.toUpperCase()}Axis${suffix}` });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (guideConfigs.grid?.horizontal?.scale === id) {
|
|
91
|
+
guides.push({ op: "rematerializeHorizontalGrid" });
|
|
92
|
+
}
|
|
93
|
+
if (guideConfigs.grid?.vertical?.scale === id) {
|
|
94
|
+
guides.push({ op: "rematerializeVerticalGrid" });
|
|
95
|
+
}
|
|
96
|
+
if (materializedLegendUsesScale(program, id)) {
|
|
97
|
+
guides.push({ op: "rematerializeLegend" });
|
|
98
|
+
}
|
|
99
|
+
return buildMaterializationPlan({ guides });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function planLayerDataRematerialization(program, id) {
|
|
103
|
+
const layer = requireLayer(program, id);
|
|
104
|
+
const scaleIds = [...new Set(
|
|
105
|
+
Object.values(layer.encoding ?? {})
|
|
106
|
+
.map(encoding => encoding?.scale)
|
|
107
|
+
.filter(scale => scale !== undefined)
|
|
108
|
+
)];
|
|
109
|
+
const scales = scaleIds.map(scale => ({
|
|
110
|
+
op: "rematerializeScale",
|
|
111
|
+
args: { id: scale, guides: false }
|
|
112
|
+
}));
|
|
113
|
+
const markStep = getMarkMaterializationStep(program, layer);
|
|
114
|
+
const marks = markStep === undefined ? [] : [markStep];
|
|
115
|
+
if (scales.length > 0 || marks.length > 0) {
|
|
116
|
+
const guides = scaleIds.flatMap(scale =>
|
|
117
|
+
planScaleGuideRematerialization(program, scale)
|
|
118
|
+
);
|
|
119
|
+
return buildMaterializationPlan({ scales, marks, guides });
|
|
120
|
+
}
|
|
121
|
+
return buildMaterializationPlan({ marks: layer.mark?.type === "point" &&
|
|
122
|
+
program.graphicSpec.objects[layer.id] !== undefined
|
|
123
|
+
? [{ op: "rematerializePointMark", args: { id: layer.id } }]
|
|
124
|
+
: [] });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function applyLayerDataRematerialization(program, id) {
|
|
128
|
+
return applyMaterializationPlan(
|
|
129
|
+
program,
|
|
130
|
+
planLayerDataRematerialization(program, id)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export { applyMaterializationPlan } from "./planner.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { hasMaterializedLegend } from "./legends.js";
|
|
2
|
+
import { requireLayer } from "../selectors/layers.js";
|
|
3
|
+
import { getMarkRematerializationStep } from "./marks.js";
|
|
4
|
+
import { buildMaterializationPlan } from "./planner.js";
|
|
5
|
+
|
|
6
|
+
export function planEncodingRematerialization(program, {
|
|
7
|
+
target,
|
|
8
|
+
channel,
|
|
9
|
+
scale
|
|
10
|
+
}) {
|
|
11
|
+
const layer = requireLayer(
|
|
12
|
+
program,
|
|
13
|
+
target,
|
|
14
|
+
`Unknown encoding materialization target "${target}"`
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const scales = [];
|
|
18
|
+
if (layer.mark?.type === "point" && scale !== undefined) {
|
|
19
|
+
scales.push({
|
|
20
|
+
op: "rematerializeScale",
|
|
21
|
+
args: { id: scale, guides: false }
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const marks = [];
|
|
26
|
+
if (
|
|
27
|
+
layer.mark?.type === "area" &&
|
|
28
|
+
channel === "color" &&
|
|
29
|
+
scale !== undefined
|
|
30
|
+
) {
|
|
31
|
+
for (const candidate of program.semanticSpec.layers) {
|
|
32
|
+
if (
|
|
33
|
+
candidate.mark?.type === "area" &&
|
|
34
|
+
candidate.encoding?.color?.scale === scale
|
|
35
|
+
) {
|
|
36
|
+
const step = getMarkRematerializationStep(candidate);
|
|
37
|
+
if (step !== undefined) marks.push(step);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
const step = getMarkRematerializationStep(layer);
|
|
42
|
+
if (step !== undefined) marks.push(step);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const guides = [];
|
|
46
|
+
if (hasMaterializedLegend(program)) {
|
|
47
|
+
guides.push({ op: "rematerializeLegend" });
|
|
48
|
+
}
|
|
49
|
+
return buildMaterializationPlan({ scales, marks, guides });
|
|
50
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findGraphicParent,
|
|
3
|
+
graphicSiblings
|
|
4
|
+
} from "../grammar/schemas/graphicTree.js";
|
|
5
|
+
import { findDataset } from "../selectors/datasets.js";
|
|
6
|
+
|
|
7
|
+
export const CANVAS_GRAPHIC_ID = "canvas";
|
|
8
|
+
export const PLOT_GRAPHIC_ID = "plot-main";
|
|
9
|
+
|
|
10
|
+
export function assertCanvasHierarchyAvailable(program) {
|
|
11
|
+
const canvasByType = Object.entries(program.graphicSpec.objects).find(
|
|
12
|
+
([, graphic]) => graphic.type === "canvas"
|
|
13
|
+
);
|
|
14
|
+
if (canvasByType !== undefined) {
|
|
15
|
+
throw new Error("createCanvas requires a program without a canvas.");
|
|
16
|
+
}
|
|
17
|
+
for (const id of [CANVAS_GRAPHIC_ID, PLOT_GRAPHIC_ID]) {
|
|
18
|
+
if (program.graphicSpec.objects[id] !== undefined) {
|
|
19
|
+
throw new Error(`createCanvas requires the reserved graphic id "${id}".`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolvePlotGraphicPlacement(program, relative = {}) {
|
|
25
|
+
const canvas = program.graphicSpec.objects[CANVAS_GRAPHIC_ID];
|
|
26
|
+
const plot = program.graphicSpec.objects[PLOT_GRAPHIC_ID];
|
|
27
|
+
|
|
28
|
+
if (canvas === undefined && plot === undefined) return relative;
|
|
29
|
+
if (canvas?.type !== "canvas") {
|
|
30
|
+
throw new Error("Ordinary mark attachment requires the canonical canvas.");
|
|
31
|
+
}
|
|
32
|
+
if (plot?.type !== "collection") {
|
|
33
|
+
throw new Error("Ordinary mark attachment requires the canonical plot container.");
|
|
34
|
+
}
|
|
35
|
+
const parent = findGraphicParent(program.graphicSpec, PLOT_GRAPHIC_ID);
|
|
36
|
+
if (parent?.kind !== "parent" || parent.id !== CANVAS_GRAPHIC_ID) {
|
|
37
|
+
throw new Error("The canonical plot container must be attached to the canvas.");
|
|
38
|
+
}
|
|
39
|
+
return { parent: PLOT_GRAPHIC_ID, ...relative };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveMarkGraphicPlacement(program, { data, markType }) {
|
|
43
|
+
const dataset = findDataset(program, data);
|
|
44
|
+
const transform = dataset?.transform?.length === 1
|
|
45
|
+
? dataset.transform[0]
|
|
46
|
+
: undefined;
|
|
47
|
+
if (transform?.type !== "regression") {
|
|
48
|
+
const axis = program.graphicSpec.objects[PLOT_GRAPHIC_ID]?.children?.find(
|
|
49
|
+
id => /^(x|y)Axis/.test(id)
|
|
50
|
+
);
|
|
51
|
+
return resolvePlotGraphicPlacement(
|
|
52
|
+
program,
|
|
53
|
+
axis === undefined ? {} : { before: axis }
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const eligible = program.semanticSpec.layers.filter(layer =>
|
|
57
|
+
layer.data === dataset.source &&
|
|
58
|
+
program.graphicSpec.objects[layer.id] !== undefined
|
|
59
|
+
);
|
|
60
|
+
const namespaced = eligible.filter(layer =>
|
|
61
|
+
`${layer.id}RegressionData` === dataset.id
|
|
62
|
+
);
|
|
63
|
+
const sources = namespaced.length === 1 ? namespaced : eligible;
|
|
64
|
+
if (sources.length !== 1) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
"Regression graphic placement requires one source mark layer."
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const anchor = sources[0].id;
|
|
70
|
+
return resolvePlotGraphicPlacement(
|
|
71
|
+
program,
|
|
72
|
+
markType === "area" ? { before: anchor } : { after: anchor }
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function resolveCanvasGraphicPlacement(program, relative = {}) {
|
|
77
|
+
const canvas = program.graphicSpec.objects[CANVAS_GRAPHIC_ID];
|
|
78
|
+
if (canvas?.type !== "canvas") {
|
|
79
|
+
throw new Error("Canvas-owned graphics require the canonical canvas.");
|
|
80
|
+
}
|
|
81
|
+
return { parent: CANVAS_GRAPHIC_ID, ...relative };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function resolveLegendGraphicPlacement(program, relative = {}) {
|
|
85
|
+
const title = program.graphicSpec.objects[CANVAS_GRAPHIC_ID]?.children?.find(
|
|
86
|
+
id => id === "chartTitle" || id === "chartSubtitle"
|
|
87
|
+
);
|
|
88
|
+
const hasRelativeAnchor = relative.before !== undefined ||
|
|
89
|
+
relative.after !== undefined;
|
|
90
|
+
return resolveCanvasGraphicPlacement(program, {
|
|
91
|
+
...(title === undefined || hasRelativeAnchor ? {} : { before: title }),
|
|
92
|
+
...relative
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function preserveGraphicPlacement(program, id) {
|
|
97
|
+
const parent = findGraphicParent(program.graphicSpec, id);
|
|
98
|
+
const siblings = graphicSiblings(program.graphicSpec, id);
|
|
99
|
+
const next = siblings[siblings.indexOf(id) + 1];
|
|
100
|
+
return {
|
|
101
|
+
...(parent?.kind === "parent" ? { parent: parent.id } : {}),
|
|
102
|
+
...(next === undefined ? {} : { before: next })
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LEGEND_CONFIG_KINDS } from "../core/vocabulary.js";
|
|
2
|
+
|
|
3
|
+
export function hasMaterializedLegend(program) {
|
|
4
|
+
return LEGEND_CONFIG_KINDS.some(kind => {
|
|
5
|
+
const config = program.guideConfigs?.legend?.[kind];
|
|
6
|
+
if (config === undefined) return false;
|
|
7
|
+
if (kind === "series" || kind === "color") {
|
|
8
|
+
return program.semanticSpec.guides.legend?.[kind] !== undefined;
|
|
9
|
+
}
|
|
10
|
+
return true;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function materializedLegendUsesScale(program, id) {
|
|
15
|
+
return LEGEND_CONFIG_KINDS.some(kind => {
|
|
16
|
+
const config = program.guideConfigs?.legend?.[kind];
|
|
17
|
+
if (config === undefined) return false;
|
|
18
|
+
return config.scale === id || config.scales?.includes(id);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { findDataset } from "../selectors/datasets.js";
|
|
2
|
+
import { isAggregate } from "../grammar/aggregate.js";
|
|
3
|
+
import { BAR_GRAINS, resolveBarGrain } from "../grammar/bars/policy.js";
|
|
4
|
+
import { resolveRuleMode } from "../grammar/rules.js";
|
|
5
|
+
import { findUpstreamTransform } from "./dataProvenance.js";
|
|
6
|
+
|
|
7
|
+
function hasPositionScales(layer) {
|
|
8
|
+
return (
|
|
9
|
+
layer.encoding?.x?.scale !== undefined &&
|
|
10
|
+
layer.encoding?.y?.scale !== undefined
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function canMaterializePoint(_program, layer) {
|
|
15
|
+
return layer.mark?.type === "point" && hasPositionScales(layer);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function canMaterializeLine(program, layer) {
|
|
19
|
+
const dataset = findDataset(program, layer.data);
|
|
20
|
+
const interval = dataset?.transform?.some(item => item.type === "interval");
|
|
21
|
+
return (
|
|
22
|
+
layer.mark?.type === "line" &&
|
|
23
|
+
hasPositionScales(layer) &&
|
|
24
|
+
(isAggregate(layer.encoding.y.aggregate) ||
|
|
25
|
+
(layer.encoding.x.fieldType === "quantitative" &&
|
|
26
|
+
layer.encoding.y.fieldType === "quantitative") ||
|
|
27
|
+
((interval || layer.encoding.y.aggregate === undefined) &&
|
|
28
|
+
["quantitative", "temporal"].includes(layer.encoding.x.fieldType) &&
|
|
29
|
+
["quantitative", "temporal"].includes(layer.encoding.y.fieldType) &&
|
|
30
|
+
layer.encoding.x.fieldType !== layer.encoding.y.fieldType))
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function canMaterializeArea(program, layer) {
|
|
35
|
+
if (layer.mark?.type !== "area" || !hasPositionScales(layer)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const dataset = findDataset(program, layer.data);
|
|
40
|
+
const densityTransform = findUpstreamTransform(program, dataset, "density");
|
|
41
|
+
const completeDensity =
|
|
42
|
+
densityTransform !== undefined &&
|
|
43
|
+
(densityTransform.groupBy === undefined ||
|
|
44
|
+
layer.encoding?.group?.field === densityTransform.groupBy);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
completeDensity ||
|
|
48
|
+
layer.encoding?.y2?.scale === layer.encoding.y.scale ||
|
|
49
|
+
layer.encoding?.x2?.scale === layer.encoding.x.scale
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function canMaterializeBar(program, layer) {
|
|
54
|
+
if (layer.mark?.type !== "bar" || !hasPositionScales(layer)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const grain = resolveBarGrain(layer);
|
|
58
|
+
return grain === BAR_GRAINS.histogram || (
|
|
59
|
+
[BAR_GRAINS.aggregate, BAR_GRAINS.ranged].includes(grain) &&
|
|
60
|
+
program.markConfigs[layer.id]?.barWidth !== undefined
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function canMaterializeRule(program, layer) {
|
|
65
|
+
const fixedSpan = program.markConfigs[layer.id]?.fixedSpan;
|
|
66
|
+
const boxSpanOwner = program.markConfigs[layer.id]?.boxSpanOwner;
|
|
67
|
+
return layer.mark?.type === "rule" && (
|
|
68
|
+
resolveRuleMode(layer) !== undefined ||
|
|
69
|
+
(boxSpanOwner !== undefined && hasPositionScales(layer)) ||
|
|
70
|
+
(fixedSpan !== undefined && hasPositionScales(layer))
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const MARK_MATERIALIZATION_POLICIES = Object.freeze({
|
|
75
|
+
point: Object.freeze({
|
|
76
|
+
canMaterialize: canMaterializePoint,
|
|
77
|
+
op: "rematerializePointMark"
|
|
78
|
+
}),
|
|
79
|
+
line: Object.freeze({
|
|
80
|
+
canMaterialize: canMaterializeLine,
|
|
81
|
+
op: "rematerializeLineMark"
|
|
82
|
+
}),
|
|
83
|
+
area: Object.freeze({
|
|
84
|
+
canMaterialize: canMaterializeArea,
|
|
85
|
+
op: "rematerializeAreaMark"
|
|
86
|
+
}),
|
|
87
|
+
bar: Object.freeze({
|
|
88
|
+
canMaterialize: canMaterializeBar,
|
|
89
|
+
op: "rematerializeBarMark"
|
|
90
|
+
}),
|
|
91
|
+
rule: Object.freeze({
|
|
92
|
+
canMaterialize: canMaterializeRule,
|
|
93
|
+
op: "rematerializeRuleMark"
|
|
94
|
+
})
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export function getMarkRematerializationStep(layer) {
|
|
98
|
+
const policy = MARK_MATERIALIZATION_POLICIES[layer.mark?.type];
|
|
99
|
+
return policy === undefined
|
|
100
|
+
? undefined
|
|
101
|
+
: { op: policy.op, args: { id: layer.id } };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getMarkMaterializationStep(program, layer) {
|
|
105
|
+
const policy = MARK_MATERIALIZATION_POLICIES[layer.mark?.type];
|
|
106
|
+
if (policy === undefined || !policy.canMaterialize(program, layer)) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
return getMarkRematerializationStep(layer);
|
|
110
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const STAGES = Object.freeze([
|
|
2
|
+
"scales",
|
|
3
|
+
"marks",
|
|
4
|
+
"guides",
|
|
5
|
+
"layout",
|
|
6
|
+
"highlights"
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
function stepKey(step) {
|
|
10
|
+
return JSON.stringify([step.op, step.args ?? null]);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function buildMaterializationPlan(stages = {}) {
|
|
14
|
+
const plan = [];
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
|
|
17
|
+
for (const stage of STAGES) {
|
|
18
|
+
for (const step of stages[stage] ?? []) {
|
|
19
|
+
if (step === undefined) continue;
|
|
20
|
+
const key = stepKey(step);
|
|
21
|
+
if (seen.has(key)) continue;
|
|
22
|
+
seen.add(key);
|
|
23
|
+
plan.push(step);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return plan;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function applyMaterializationPlan(program, plan) {
|
|
31
|
+
let next = program;
|
|
32
|
+
for (const step of buildMaterializationPlan({ marks: plan })) {
|
|
33
|
+
next = step.args === undefined
|
|
34
|
+
? next[step.op]()
|
|
35
|
+
: next[step.op](step.args);
|
|
36
|
+
}
|
|
37
|
+
return next;
|
|
38
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isPlainObject } from "../../core/immutable.js";
|
|
2
|
+
import {
|
|
3
|
+
normalizeMarkSelector,
|
|
4
|
+
selectMarkItemKeys
|
|
5
|
+
} from "../../grammar/markSelection.js";
|
|
6
|
+
import { findDataset } from "../../selectors/datasets.js";
|
|
7
|
+
import { findLayer } from "../../selectors/layers.js";
|
|
8
|
+
import { resolveMarkSelection } from "./state.js";
|
|
9
|
+
|
|
10
|
+
function resolvePointRows(program, layer, selector) {
|
|
11
|
+
const dataset = findDataset(program, layer.data);
|
|
12
|
+
if (dataset === undefined) {
|
|
13
|
+
throw new Error(`Point mark "${layer.id}" requires an existing dataset.`);
|
|
14
|
+
}
|
|
15
|
+
const items = dataset.values.map((row, index) => ({
|
|
16
|
+
key: `${layer.id}/point/${index}`,
|
|
17
|
+
fields: isPlainObject(row) ? { ...row } : {},
|
|
18
|
+
channels: {},
|
|
19
|
+
properties: {},
|
|
20
|
+
members: [row]
|
|
21
|
+
}));
|
|
22
|
+
return {
|
|
23
|
+
selector,
|
|
24
|
+
items,
|
|
25
|
+
keys: selectMarkItemKeys(items, selector)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function resolveMarkFilterSelection(program, target, selector) {
|
|
30
|
+
const normalized = normalizeMarkSelector(selector);
|
|
31
|
+
const layer = findLayer(program, target);
|
|
32
|
+
if (
|
|
33
|
+
layer?.mark?.type === "point" &&
|
|
34
|
+
normalized.grain === "item" &&
|
|
35
|
+
normalized.field !== undefined
|
|
36
|
+
) {
|
|
37
|
+
return resolvePointRows(program, layer, normalized);
|
|
38
|
+
}
|
|
39
|
+
return resolveMarkSelection(program, target, normalized);
|
|
40
|
+
}
|