ggaction 0.0.3 → 0.0.5

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.
Files changed (223) hide show
  1. package/CHANGELOG.md +58 -3
  2. package/README.md +117 -83
  3. package/package.json +27 -11
  4. package/src/actions/boxPlots/create.js +13 -7
  5. package/src/actions/boxPlots/edit.js +31 -35
  6. package/src/actions/boxPlots/materialize.js +3 -0
  7. package/src/actions/boxPlots/options.js +8 -1
  8. package/src/actions/boxPlots/resolve.js +10 -2
  9. package/src/actions/charts/bar.js +50 -0
  10. package/src/actions/charts/heatmap.js +205 -0
  11. package/src/actions/charts/histogram.js +58 -0
  12. package/src/actions/charts/index.js +15 -0
  13. package/src/actions/charts/line.js +69 -0
  14. package/src/actions/charts/parallel.js +76 -0
  15. package/src/actions/charts/scatter.js +56 -0
  16. package/src/actions/charts/shared.js +121 -0
  17. package/src/actions/coordinates/parallel.js +50 -0
  18. package/src/actions/data/bin2d.js +137 -0
  19. package/src/actions/data/density.js +54 -13
  20. package/src/actions/data/derived.js +22 -0
  21. package/src/actions/data/filter.js +5 -0
  22. package/src/actions/data/gradientProfile.js +65 -0
  23. package/src/actions/data/horizon.js +71 -0
  24. package/src/actions/data/index.js +29 -2
  25. package/src/actions/data/regression.js +1 -1
  26. package/src/actions/data/window.js +50 -0
  27. package/src/actions/encodings/appearance.js +1 -1
  28. package/src/actions/encodings/color/continuous.js +137 -0
  29. package/src/actions/encodings/color/index.js +167 -0
  30. package/src/actions/encodings/color/layout.js +99 -0
  31. package/src/actions/encodings/color/policy.js +88 -0
  32. package/src/actions/encodings/density/resolve.js +109 -0
  33. package/src/actions/encodings/density.js +193 -88
  34. package/src/actions/encodings/horizon/resolve.js +283 -0
  35. package/src/actions/encodings/horizon.js +364 -0
  36. package/src/actions/encodings/index.js +8 -2
  37. package/src/actions/encodings/offset.js +1 -1
  38. package/src/actions/encodings/parallel.js +115 -0
  39. package/src/actions/encodings/pathOrder.js +131 -0
  40. package/src/actions/encodings/position/apply.js +73 -4
  41. package/src/actions/encodings/{position.js → position/index.js} +21 -8
  42. package/src/actions/encodings/position/policies/arc.js +27 -4
  43. package/src/actions/encodings/position/policies/area.js +33 -2
  44. package/src/actions/encodings/position/policies/index.js +7 -2
  45. package/src/actions/encodings/position/policies/line.js +18 -2
  46. package/src/actions/encodings/position/resolve.js +25 -3
  47. package/src/actions/encodings/ranged.js +5 -3
  48. package/src/actions/encodings/ruleAppearance.js +88 -10
  49. package/src/actions/encodings/strokeDash.js +1 -1
  50. package/src/actions/encodings/text.js +1 -1
  51. package/src/actions/errorBands/options.js +1 -1
  52. package/src/actions/errorBars/options.js +1 -1
  53. package/src/actions/facets/actions.js +2 -3
  54. package/src/actions/facets/derive.js +35 -5
  55. package/src/actions/facets/guides.js +1 -1
  56. package/src/actions/facets/replay.js +7 -36
  57. package/src/actions/gradientPlots/components.js +177 -0
  58. package/src/actions/gradientPlots/create.js +96 -0
  59. package/src/actions/gradientPlots/edit.js +153 -0
  60. package/src/actions/gradientPlots/index.js +24 -0
  61. package/src/actions/gradientPlots/materialize.js +287 -0
  62. package/src/actions/gradientPlots/options.js +120 -0
  63. package/src/actions/gradientPlots/paint.js +107 -0
  64. package/src/actions/gradientPlots/rebind.js +51 -0
  65. package/src/actions/gradientPlots/resolve.js +81 -0
  66. package/src/actions/guides/applicability.js +44 -17
  67. package/src/actions/guides/axes/axes.js +37 -3
  68. package/src/actions/guides/axes/index.js +2 -0
  69. package/src/actions/guides/axes/labels.js +12 -7
  70. package/src/actions/guides/axes/parallel/resolve.js +86 -0
  71. package/src/actions/guides/axes/parallel.js +228 -0
  72. package/src/actions/guides/axes/ticks.js +1 -1
  73. package/src/actions/guides/axes/titles.js +12 -9
  74. package/src/actions/guides/grids/resolve.js +1 -1
  75. package/src/actions/guides/guides.js +60 -5
  76. package/src/actions/guides/legends/categorical/actions.js +48 -11
  77. package/src/actions/guides/legends/categorical/layout.js +4 -2
  78. package/src/actions/guides/legends/continuous/interval.js +1 -1
  79. package/src/actions/guides/legends/continuous/opacity.js +1 -1
  80. package/src/actions/guides/legends/edit.js +7 -20
  81. package/src/actions/guides/legends/index.js +2 -0
  82. package/src/actions/guides/legends/remove.js +6 -31
  83. package/src/actions/guides/legends/size.js +1 -1
  84. package/src/actions/guides/legends/strokeWidth.js +170 -0
  85. package/src/actions/guides/legends/target.js +25 -0
  86. package/src/actions/guides/polar/{axes.js → axes/index.js} +5 -5
  87. package/src/actions/guides/polar/index.js +1 -1
  88. package/src/actions/guides/polar/resolve.js +1 -1
  89. package/src/actions/guides/tickValues.js +1 -1
  90. package/src/actions/index.js +6 -0
  91. package/src/actions/intervals/resolve.js +1 -1
  92. package/src/actions/marks/arc/actions.js +2 -2
  93. package/src/actions/marks/area/actions.js +61 -137
  94. package/src/actions/marks/area/materialize.js +163 -0
  95. package/src/actions/marks/bar/edit.js +1 -1
  96. package/src/actions/marks/index.js +5 -1
  97. package/src/actions/marks/line/actions.js +100 -81
  98. package/src/actions/marks/line/materialize.js +146 -0
  99. package/src/actions/marks/point/create.js +76 -0
  100. package/src/actions/marks/point/edit.js +85 -0
  101. package/src/actions/marks/point/index.js +11 -1
  102. package/src/actions/marks/point/jitter.js +72 -0
  103. package/src/actions/marks/point/{actions.js → materialize.js} +175 -170
  104. package/src/actions/marks/rect/actions.js +4 -1
  105. package/src/actions/marks/remove.js +13 -2
  106. package/src/actions/marks/rule/actions.js +26 -4
  107. package/src/actions/marks/shared.js +1 -1
  108. package/src/actions/marks/text/actions.js +20 -5
  109. package/src/actions/marks/text/index.js +7 -1
  110. package/src/actions/marks/text/layout.js +321 -0
  111. package/src/actions/primitives/semanticValidation/dataset.js +22 -0
  112. package/src/actions/primitives/semanticValidation/guide.js +43 -0
  113. package/src/actions/primitives/semanticValidation/index.js +23 -0
  114. package/src/actions/primitives/semanticValidation/layer.js +74 -0
  115. package/src/actions/primitives/semanticValidation/scale.js +74 -0
  116. package/src/actions/primitives/semanticValidation/shared.js +5 -0
  117. package/src/actions/primitives/semanticValue.js +1 -204
  118. package/src/actions/regression/components.js +1 -1
  119. package/src/actions/regression/create.js +1 -1
  120. package/src/actions/regression/edit.js +20 -22
  121. package/src/actions/scales/consumers/common.js +109 -0
  122. package/src/actions/scales/consumers/families.js +58 -0
  123. package/src/actions/scales/consumers/index.js +41 -0
  124. package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +10 -132
  125. package/src/actions/scales/create.js +1 -1
  126. package/src/actions/scales/definitions.js +32 -2
  127. package/src/actions/scales/edit.js +8 -206
  128. package/src/actions/scales/editPolicy.js +214 -0
  129. package/src/actions/scales/materialize.js +3 -3
  130. package/src/actions/scales/patch.js +26 -0
  131. package/src/actions/selection/actions.js +28 -3
  132. package/src/actions/violinPlots/create.js +198 -0
  133. package/src/actions/violinPlots/index.js +5 -0
  134. package/src/core/ChartProgram.js +22 -236
  135. package/src/core/compositionState.js +169 -0
  136. package/src/core/materializationState.js +65 -0
  137. package/src/core/programState.js +28 -0
  138. package/src/core/textMetrics.js +47 -0
  139. package/src/core/vocabulary.js +7 -2
  140. package/src/grammar/arcs.js +50 -13
  141. package/src/grammar/areaSeries.js +42 -6
  142. package/src/grammar/bars/aggregate.js +1 -1
  143. package/src/grammar/bin2d.js +333 -0
  144. package/src/grammar/categoricalDensity.js +192 -0
  145. package/src/grammar/coordinates.js +1 -1
  146. package/src/grammar/curveCommands.js +3 -1
  147. package/src/grammar/density.js +253 -27
  148. package/src/grammar/facets/dependencies.js +3 -11
  149. package/src/grammar/{facets.js → facets/index.js} +8 -6
  150. package/src/grammar/gradientProfile.js +213 -0
  151. package/src/grammar/horizon.js +455 -0
  152. package/src/grammar/interval.js +2 -96
  153. package/src/grammar/jitter.js +197 -0
  154. package/src/grammar/lineSeries.js +70 -1
  155. package/src/grammar/paint.js +88 -0
  156. package/src/grammar/palettes.js +5 -7
  157. package/src/grammar/parallelCoordinates.js +213 -0
  158. package/src/grammar/pathOrder.js +35 -0
  159. package/src/grammar/pointShapes.js +24 -0
  160. package/src/grammar/polarLineCommands.js +1 -1
  161. package/src/grammar/regression/derive.js +97 -0
  162. package/src/grammar/regression/index.js +22 -0
  163. package/src/grammar/regression/models.js +282 -0
  164. package/src/grammar/regression/parameters.js +106 -0
  165. package/src/grammar/rules.js +2 -1
  166. package/src/grammar/scales/appearance.js +21 -0
  167. package/src/grammar/scales/color.js +4 -2
  168. package/src/grammar/schemas/concreteGraphic.js +11 -1
  169. package/src/grammar/schemas/graphicBounds.js +19 -37
  170. package/src/grammar/schemas/semanticPath.js +17 -2
  171. package/src/grammar/statistics/studentT.js +98 -0
  172. package/src/grammar/transforms.js +101 -12
  173. package/src/grammar/window.js +339 -0
  174. package/src/layout/labels.js +270 -0
  175. package/src/layout/text.js +4 -1
  176. package/src/layout/title.js +10 -20
  177. package/src/materialization/bars/aggregate.js +1 -1
  178. package/src/materialization/bars/grouped.js +1 -1
  179. package/src/materialization/bars/histogram.js +1 -1
  180. package/src/materialization/bars/ranged.js +1 -1
  181. package/src/materialization/dataProvenance.js +56 -2
  182. package/src/materialization/density.js +1 -1
  183. package/src/materialization/dependencies.js +15 -101
  184. package/src/materialization/encodings.js +2 -2
  185. package/src/materialization/facetGuides/index.js +2 -0
  186. package/src/materialization/facetGuides/legacyCategorical.js +125 -0
  187. package/src/materialization/facetGuides/placement.js +126 -0
  188. package/src/materialization/facetGuides/preparation.js +188 -0
  189. package/src/materialization/facets.js +1 -1
  190. package/src/materialization/guides/resources.js +74 -24
  191. package/src/materialization/horizon.js +29 -0
  192. package/src/materialization/layout.js +15 -0
  193. package/src/materialization/marks/capabilities.js +161 -0
  194. package/src/materialization/marks/index.js +108 -0
  195. package/src/materialization/marks/pathOrder.js +71 -0
  196. package/src/materialization/marks/policies.js +91 -0
  197. package/src/materialization/rect.js +1 -1
  198. package/src/materialization/rowEncoding.js +1 -1
  199. package/src/materialization/scaleGuideDependencies.js +102 -0
  200. package/src/materialization/scales/map.js +1 -1
  201. package/src/materialization/scales/policies/series.js +1 -1
  202. package/src/materialization/scales/resolve.js +3 -1
  203. package/src/materialization/selection/items/bar.js +1 -1
  204. package/src/materialization/selection/items/path.js +27 -1
  205. package/src/materialization/selection/items/point.js +1 -5
  206. package/src/materialization/selection/items/rect.js +21 -0
  207. package/src/materialization/selection/styles.js +21 -10
  208. package/src/materialization/text.js +12 -4
  209. package/src/renderers/canvas/fill.js +33 -0
  210. package/src/renderers/canvas/index.js +7 -0
  211. package/src/renderers/canvas/path.js +7 -4
  212. package/src/renderers/canvas/rect.js +9 -3
  213. package/src/renderers/canvas/text.js +14 -1
  214. package/src/selectors/datasets.js +4 -0
  215. package/src/theme/defaults.js +4 -0
  216. package/types/extension.d.ts +10 -1
  217. package/types/index.d.ts +76 -0
  218. package/types/program.d.ts +632 -10
  219. package/src/actions/encodings/color.js +0 -424
  220. package/src/grammar/regression.js +0 -578
  221. package/src/grammar/scales.js +0 -1
  222. package/src/materialization/facetGuides.js +0 -426
  223. package/src/materialization/marks.js +0 -316
@@ -5,27 +5,66 @@ const CATEGORICAL_COMPONENTS = Object.freeze([
5
5
  "Labels", "Title", "Background"
6
6
  ]);
7
7
 
8
- const LEGEND_GRAPHICS = Object.freeze({
9
- series: Object.freeze(CATEGORICAL_COMPONENTS.map(
10
- component => `seriesLegend${component}`
11
- )),
12
- color: Object.freeze(CATEGORICAL_COMPONENTS.map(
13
- component => `colorLegend${component}`
14
- )),
15
- size: Object.freeze([
16
- "sizeLegendSymbols", "sizeLegendLabels", "sizeLegendTitle"
17
- ]),
18
- gradient: Object.freeze([
19
- "colorGradientBackground", "colorGradientStrips", "colorGradientTicks",
20
- "colorGradientLabels", "colorGradientTitle"
21
- ]),
22
- interval: Object.freeze([
23
- "colorLegendSymbols", "colorLegendLabels", "colorLegendTitle"
24
- ]),
25
- opacity: Object.freeze([
26
- "opacityLegendBackground", "opacityLegendSymbols", "opacityLegendLabels",
27
- "opacityLegendTitle"
28
- ])
8
+ const LEGEND_RESOURCE_POLICIES = Object.freeze({
9
+ series: Object.freeze({
10
+ semanticKind: "series",
11
+ family: "categorical",
12
+ rematerializeOp: undefined,
13
+ graphicIds: Object.freeze(CATEGORICAL_COMPONENTS.map(
14
+ component => `seriesLegend${component}`
15
+ ))
16
+ }),
17
+ color: Object.freeze({
18
+ semanticKind: "color",
19
+ family: "categorical",
20
+ rematerializeOp: undefined,
21
+ graphicIds: Object.freeze(CATEGORICAL_COMPONENTS.map(
22
+ component => `colorLegend${component}`
23
+ ))
24
+ }),
25
+ size: Object.freeze({
26
+ semanticKind: "size",
27
+ family: "size",
28
+ rematerializeOp: "rematerializeSizeLegend",
29
+ graphicIds: Object.freeze([
30
+ "sizeLegendSymbols", "sizeLegendLabels", "sizeLegendTitle"
31
+ ])
32
+ }),
33
+ gradient: Object.freeze({
34
+ semanticKind: "color",
35
+ family: "continuous",
36
+ rematerializeOp: "rematerializeGradientLegend",
37
+ graphicIds: Object.freeze([
38
+ "colorGradientBackground", "colorGradientStrips", "colorGradientTicks",
39
+ "colorGradientLabels", "colorGradientTitle"
40
+ ])
41
+ }),
42
+ interval: Object.freeze({
43
+ semanticKind: "color",
44
+ family: "interval",
45
+ rematerializeOp: "rematerializeIntervalLegend",
46
+ graphicIds: Object.freeze([
47
+ "colorLegendSymbols", "colorLegendLabels", "colorLegendTitle"
48
+ ])
49
+ }),
50
+ opacity: Object.freeze({
51
+ semanticKind: "opacity",
52
+ family: "continuous",
53
+ rematerializeOp: "rematerializeOpacityLegend",
54
+ graphicIds: Object.freeze([
55
+ "opacityLegendBackground", "opacityLegendSymbols", "opacityLegendLabels",
56
+ "opacityLegendTitle"
57
+ ])
58
+ }),
59
+ strokeWidth: Object.freeze({
60
+ semanticKind: "strokeWidth",
61
+ family: "strokeWidth",
62
+ rematerializeOp: "rematerializeStrokeWidthLegend",
63
+ graphicIds: Object.freeze([
64
+ "strokeWidthLegendSymbols", "strokeWidthLegendLabels",
65
+ "strokeWidthLegendTitle"
66
+ ])
67
+ })
29
68
  });
30
69
 
31
70
  export function axisGraphicIds(channel) {
@@ -34,11 +73,22 @@ export function axisGraphicIds(channel) {
34
73
  }
35
74
 
36
75
  export function legendGraphicIds(kind) {
37
- const ids = LEGEND_GRAPHICS[kind];
38
- if (ids === undefined) {
76
+ return legendResourcePolicy(kind).graphicIds;
77
+ }
78
+
79
+ export function legendResourcePolicy(kind) {
80
+ const policy = LEGEND_RESOURCE_POLICIES[kind];
81
+ if (policy === undefined) {
39
82
  throw new Error(`Unknown legend graphic kind "${kind}".`);
40
83
  }
41
- return ids;
84
+ return policy;
85
+ }
86
+
87
+ export function legendResourcePolicies() {
88
+ return Object.entries(LEGEND_RESOURCE_POLICIES).map(([kind, policy]) => ({
89
+ kind,
90
+ ...policy
91
+ }));
42
92
  }
43
93
 
44
94
  export function allLegendGraphicIds(kinds) {
@@ -0,0 +1,29 @@
1
+ import { getMarkMaterializationStep } from "./marks/index.js";
2
+ import { buildMaterializationPlan } from "./planner.js";
3
+ import { requireLayer } from "../selectors/layers.js";
4
+
5
+ export function planHorizonRematerialization(program, target) {
6
+ const layer = requireLayer(
7
+ program,
8
+ target,
9
+ `Unknown Horizon materialization target "${target}"`
10
+ );
11
+ const scaleIds = new Set([
12
+ layer.encoding?.x?.scale,
13
+ layer.encoding?.y?.scale,
14
+ layer.encoding?.color?.scale
15
+ ].filter(Boolean));
16
+ const ordered = [
17
+ layer,
18
+ ...program.semanticSpec.layers.filter(candidate => candidate.id !== target)
19
+ ];
20
+ const marks = ordered.flatMap(candidate => {
21
+ const consumesScale = Object.values(candidate.encoding ?? {}).some(
22
+ encoding => scaleIds.has(encoding?.scale)
23
+ );
24
+ if (!consumesScale) return [];
25
+ const step = getMarkMaterializationStep(program, candidate);
26
+ return step === undefined ? [] : [step];
27
+ });
28
+ return buildMaterializationPlan({ marks });
29
+ }
@@ -0,0 +1,15 @@
1
+ const LAYOUT_CONSUMER_POLICIES = Object.freeze([
2
+ Object.freeze({
3
+ applies(program) {
4
+ return program.semanticSpec.title.text !== undefined &&
5
+ program.titleConfig !== undefined;
6
+ },
7
+ step: Object.freeze({ op: "rematerializeTitle" })
8
+ })
9
+ ]);
10
+
11
+ export function planLayoutRematerialization(program) {
12
+ return LAYOUT_CONSUMER_POLICIES.flatMap(policy =>
13
+ policy.applies(program) ? [policy.step] : []
14
+ );
15
+ }
@@ -0,0 +1,161 @@
1
+ import { findDataset } from "../../selectors/datasets.js";
2
+ import { findLayer } from "../../selectors/layers.js";
3
+ import { findCoordinate } from "../../selectors/coordinates.js";
4
+ import { isAggregate } from "../../grammar/aggregate.js";
5
+ import {
6
+ BAR_GRAINS,
7
+ resolveBarColorLayout,
8
+ resolveBarOffsetChannel,
9
+ resolveBarGrain
10
+ } from "../../grammar/bars/policy.js";
11
+ import { resolveRuleMode } from "../../grammar/rules.js";
12
+ import { findUpstreamTransform } from "../dataProvenance.js";
13
+ import { resolveRectMode } from "../../grammar/rects.js";
14
+
15
+ function hasCartesianPositionScales(layer) {
16
+ return (
17
+ layer.encoding?.x?.scale !== undefined &&
18
+ layer.encoding?.y?.scale !== undefined
19
+ );
20
+ }
21
+
22
+ function hasPolarPositionScales(layer) {
23
+ return (
24
+ layer.encoding?.theta?.scale !== undefined &&
25
+ layer.encoding?.radius?.scale !== undefined
26
+ );
27
+ }
28
+
29
+ export function canMaterializePoint(_program, layer) {
30
+ return layer.mark?.type === "point" && (
31
+ hasCartesianPositionScales(layer) || hasPolarPositionScales(layer)
32
+ );
33
+ }
34
+
35
+ export function canMaterializeLine(program, layer) {
36
+ const parallel = layer.encoding?.parallel;
37
+ if (parallel !== undefined) {
38
+ const coordinate = findCoordinate(program, layer.coordinate);
39
+ return layer.mark?.type === "line" &&
40
+ coordinate?.type === "parallel" &&
41
+ parallel.dimensions?.length >= 2 &&
42
+ parallel.dimensions.every(dimension => dimension.scale !== undefined);
43
+ }
44
+ const dataset = findDataset(program, layer.data);
45
+ const interval = dataset?.transform?.some(item => item.type === "interval");
46
+ if (hasPolarPositionScales(layer)) {
47
+ return (
48
+ layer.mark?.type === "line" &&
49
+ ["quantitative", "temporal", "ordinal", "nominal"].includes(
50
+ layer.encoding.theta.fieldType
51
+ ) &&
52
+ layer.encoding.radius.fieldType === "quantitative"
53
+ );
54
+ }
55
+ return (
56
+ layer.mark?.type === "line" &&
57
+ hasCartesianPositionScales(layer) &&
58
+ (isAggregate(layer.encoding.y.aggregate) ||
59
+ (layer.encoding.x.fieldType === "quantitative" &&
60
+ layer.encoding.y.fieldType === "quantitative") ||
61
+ ((interval || layer.encoding.y.aggregate === undefined) &&
62
+ ["quantitative", "temporal"].includes(layer.encoding.x.fieldType) &&
63
+ ["quantitative", "temporal"].includes(layer.encoding.y.fieldType) &&
64
+ layer.encoding.x.fieldType !== layer.encoding.y.fieldType))
65
+ );
66
+ }
67
+
68
+ export function canMaterializeArea(program, layer) {
69
+ if (layer.mark?.type !== "area" || !hasCartesianPositionScales(layer)) {
70
+ return false;
71
+ }
72
+ const dataset = findDataset(program, layer.data);
73
+ const densityTransform = findUpstreamTransform(program, dataset, "density");
74
+ if (isIntentionallyEmptyArea(program, layer)) return false;
75
+ const completeDensity =
76
+ densityTransform !== undefined &&
77
+ (densityTransform.groupBy === undefined ||
78
+ layer.encoding?.group?.field === densityTransform.groupBy);
79
+ return (
80
+ completeDensity ||
81
+ layer.encoding?.y2?.scale === layer.encoding.y.scale ||
82
+ layer.encoding?.x2?.scale === layer.encoding.x.scale
83
+ );
84
+ }
85
+
86
+ export function isIntentionallyEmptyArea(program, layer) {
87
+ if (
88
+ layer.mark?.type !== "area" ||
89
+ !Array.isArray(program.semanticSpec?.datasets)
90
+ ) return false;
91
+ const dataset = findDataset(program, layer.data);
92
+ return dataset?.values?.length === 0 &&
93
+ findUpstreamTransform(program, dataset, "horizon") !== undefined;
94
+ }
95
+
96
+ export function canMaterializeArc(_program, layer) {
97
+ if (
98
+ layer.mark?.type !== "arc" ||
99
+ layer.encoding?.theta?.scale === undefined ||
100
+ !["nominal", "ordinal"].includes(layer.encoding.theta.fieldType)
101
+ ) {
102
+ return false;
103
+ }
104
+ if (["count", "sum"].includes(layer.encoding.theta.aggregate)) {
105
+ return layer.encoding?.radius === undefined;
106
+ }
107
+ return (
108
+ layer.encoding.theta.aggregate === undefined &&
109
+ layer.encoding?.radius?.scale !== undefined &&
110
+ layer.encoding.radius.fieldType === "quantitative"
111
+ );
112
+ }
113
+
114
+ export function canMaterializeBar(program, layer) {
115
+ if (layer.mark?.type !== "bar" || !hasCartesianPositionScales(layer)) {
116
+ return false;
117
+ }
118
+ const grain = resolveBarGrain(layer);
119
+ if (![BAR_GRAINS.histogram, BAR_GRAINS.aggregate, BAR_GRAINS.ranged].includes(grain)) {
120
+ return false;
121
+ }
122
+ if (
123
+ grain === BAR_GRAINS.aggregate &&
124
+ resolveBarColorLayout(layer) === "group"
125
+ ) {
126
+ const offsetChannel = resolveBarOffsetChannel(layer);
127
+ return (
128
+ layer.encoding?.color?.field !== undefined &&
129
+ layer.encoding?.[offsetChannel]?.field === layer.encoding.color.field &&
130
+ layer.encoding[offsetChannel].scale !== undefined
131
+ );
132
+ }
133
+ return true;
134
+ }
135
+
136
+ export function canMaterializeRule(program, layer) {
137
+ const fixedSpan = program.markConfigs[layer.id]?.fixedSpan;
138
+ const boxSpanOwner = program.markConfigs[layer.id]?.boxSpanOwner;
139
+ return layer.mark?.type === "rule" && (
140
+ resolveRuleMode(layer) !== undefined ||
141
+ (boxSpanOwner !== undefined && hasCartesianPositionScales(layer)) ||
142
+ (fixedSpan !== undefined && hasCartesianPositionScales(layer))
143
+ );
144
+ }
145
+
146
+ export function canMaterializeText(program, layer) {
147
+ if (layer.mark?.type !== "text" || layer.encoding?.text === undefined) {
148
+ return false;
149
+ }
150
+ if (layer.source !== undefined) {
151
+ const source = findLayer(program, layer.source);
152
+ return source !== undefined &&
153
+ ["point", "bar", "rule", "rect"].includes(source.mark?.type) &&
154
+ Array.isArray(program.graphicSpec.objects[source.id]?.items);
155
+ }
156
+ return hasCartesianPositionScales(layer);
157
+ }
158
+
159
+ export function canMaterializeRect(_program, layer) {
160
+ return resolveRectMode(layer) !== undefined;
161
+ }
@@ -0,0 +1,108 @@
1
+ import { POSITION_CHANNELS } from "../../core/vocabulary.js";
2
+ import { getMarkMaterializationPolicy } from "./policies.js";
3
+
4
+ export {
5
+ canMaterializeArc,
6
+ canMaterializeArea,
7
+ canMaterializeBar,
8
+ canMaterializeLine,
9
+ canMaterializePoint,
10
+ canMaterializeRect,
11
+ canMaterializeRule,
12
+ canMaterializeText
13
+ } from "./capabilities.js";
14
+
15
+ export function getMarkRematerializationStep(layer) {
16
+ const policy = getMarkMaterializationPolicy(layer);
17
+ return policy === undefined
18
+ ? undefined
19
+ : { op: policy.op, args: { id: layer.id } };
20
+ }
21
+
22
+ export function getMarkMaterializationStep(program, layer) {
23
+ const policy = getMarkMaterializationPolicy(layer);
24
+ if (policy === undefined || !policy.canMaterialize(program, layer)) {
25
+ return undefined;
26
+ }
27
+ return getMarkRematerializationStep(layer);
28
+ }
29
+
30
+ export function getSourceDependentMarkSteps(program, sourceId) {
31
+ return program.semanticSpec.layers.flatMap(layer =>
32
+ layer.source === sourceId &&
33
+ getMarkMaterializationPolicy(layer)?.sourceDependent === true
34
+ ? [getMarkMaterializationStep(program, layer)].filter(
35
+ step => step !== undefined
36
+ )
37
+ : []
38
+ );
39
+ }
40
+
41
+ export function getPositionEncodingMaterializationSteps(program, layer, scaleId) {
42
+ const policy = getMarkMaterializationPolicy(layer);
43
+ if (policy === undefined) return [];
44
+ const scale = { op: "rematerializeScale", args: { id: scaleId } };
45
+ const complete = policy.canMaterialize(program, layer);
46
+ const mark = getMarkRematerializationStep(layer);
47
+ if (!complete) {
48
+ if (policy.positionEncoding.incomplete === "scale") return [scale];
49
+ return policy.positionEncoding.scaleFirst ? [scale, mark] : [mark];
50
+ }
51
+ return policy.positionEncoding.scaleFirst ? [scale, mark] : [mark];
52
+ }
53
+
54
+ export function getScaleConsumerMaterializationMode(layer, channel) {
55
+ const policy = getMarkMaterializationPolicy(layer);
56
+ if (policy === undefined) return "direct";
57
+ if (POSITION_CHANNELS.includes(channel)) {
58
+ return policy.scaleApplication.position ?? policy.scaleApplication.default;
59
+ }
60
+ if (policy.scaleApplication.deferredChannels?.includes(channel)) {
61
+ return "defer";
62
+ }
63
+ return policy.scaleApplication.default;
64
+ }
65
+
66
+ export function canDeferScaleConsumerApplication(layer) {
67
+ return getMarkMaterializationPolicy(layer)?.scaleApplication.deferWithMark === true;
68
+ }
69
+
70
+ export function getExistingMarkRematerializationStep(program, layer) {
71
+ const policy = getMarkMaterializationPolicy(layer);
72
+ if (
73
+ policy?.rematerializeIncompleteExisting !== true ||
74
+ program.graphicSpec.objects[layer.id] === undefined
75
+ ) {
76
+ return undefined;
77
+ }
78
+ return getMarkRematerializationStep(layer);
79
+ }
80
+
81
+ export function getEncodingMaterializationStages(program, layer, channel, scale) {
82
+ const policy = getMarkMaterializationPolicy(layer);
83
+ if (policy === undefined) return { scales: [], marks: [] };
84
+ const scales = policy.encoding?.scaleFirst === true && scale !== undefined
85
+ ? [{
86
+ op: "rematerializeScale",
87
+ args: { id: scale, guides: false, marks: false }
88
+ }]
89
+ : [];
90
+ const shared = policy.encoding?.sharedChannels?.includes(channel) === true &&
91
+ scale !== undefined;
92
+ const candidates = shared
93
+ ? program.semanticSpec.layers.filter(candidate =>
94
+ candidate.mark?.type === layer.mark?.type &&
95
+ candidate.encoding?.[channel]?.scale === scale
96
+ )
97
+ : [layer];
98
+ const marks = candidates.flatMap(candidate => {
99
+ if (policy.encoding?.skipRematerialization?.(program, candidate) === true) {
100
+ return [];
101
+ }
102
+ const step = policy.encoding?.completeOnly === true
103
+ ? getMarkMaterializationStep(program, candidate)
104
+ : getMarkRematerializationStep(candidate);
105
+ return step === undefined ? [] : [step];
106
+ });
107
+ return { scales, marks };
108
+ }
@@ -0,0 +1,71 @@
1
+ import { isAggregate } from "../../grammar/aggregate.js";
2
+ import { findTransformPolicy } from "../../grammar/transforms.js";
3
+ import { findDataset } from "../../selectors/datasets.js";
4
+
5
+ function hasRowPreservingProvenance(program, dataset) {
6
+ const visited = new Set();
7
+ let current = dataset;
8
+ while (current?.source !== undefined) {
9
+ if (visited.has(current.id) || current.transform?.length !== 1) return false;
10
+ visited.add(current.id);
11
+ const policy = findTransformPolicy(current.transform[0].type);
12
+ if (
13
+ policy?.facetTopology !== "rowPreserving" &&
14
+ policy?.provenanceTransparent !== true
15
+ ) {
16
+ return false;
17
+ }
18
+ current = findDataset(program, current.source);
19
+ if (current === undefined) return false;
20
+ }
21
+ return current !== undefined;
22
+ }
23
+
24
+ function isPolar(layer) {
25
+ return layer.encoding?.theta !== undefined ||
26
+ layer.encoding?.radius !== undefined;
27
+ }
28
+
29
+ function isCompositePath(program, layer) {
30
+ const config = program.markConfigs[layer.id];
31
+ return config?.errorBand !== undefined ||
32
+ config?.errorBandBoundary !== undefined ||
33
+ config?.regression !== undefined;
34
+ }
35
+
36
+ export function pathOrderCompatibility(program, layer) {
37
+ if (!["line", "area"].includes(layer?.mark?.type)) {
38
+ return { compatible: false, reason: "requires a line or area mark" };
39
+ }
40
+ if (isPolar(layer)) {
41
+ return { compatible: false, reason: "does not support Polar paths" };
42
+ }
43
+ const dataset = findDataset(program, layer.data);
44
+ if (dataset === undefined) {
45
+ return { compatible: false, reason: "requires an existing dataset" };
46
+ }
47
+ if (!hasRowPreservingProvenance(program, dataset)) {
48
+ return {
49
+ compatible: false,
50
+ reason: "requires raw or row-preserving source data"
51
+ };
52
+ }
53
+ if (isCompositePath(program, layer)) {
54
+ return {
55
+ compatible: false,
56
+ reason: "does not support generated error or regression paths"
57
+ };
58
+ }
59
+ if (layer.mark.type === "line" && isAggregate(layer.encoding?.y?.aggregate)) {
60
+ return { compatible: false, reason: "does not support aggregate lines" };
61
+ }
62
+ return { compatible: true, dataset };
63
+ }
64
+
65
+ export function assertPathOrderCompatible(program, layer, operation) {
66
+ const result = pathOrderCompatibility(program, layer);
67
+ if (!result.compatible) {
68
+ throw new Error(`${operation} ${result.reason} on mark "${layer?.id}".`);
69
+ }
70
+ return result.dataset;
71
+ }
@@ -0,0 +1,91 @@
1
+ import {
2
+ canMaterializeArc,
3
+ canMaterializeArea,
4
+ canMaterializeBar,
5
+ canMaterializeLine,
6
+ canMaterializePoint,
7
+ canMaterializeRect,
8
+ canMaterializeRule,
9
+ canMaterializeText,
10
+ isIntentionallyEmptyArea
11
+ } from "./capabilities.js";
12
+
13
+ const MARK_MATERIALIZATION_POLICIES = Object.freeze({
14
+ point: Object.freeze({
15
+ canMaterialize: canMaterializePoint,
16
+ op: "rematerializePointMark",
17
+ positionEncoding: Object.freeze({ incomplete: "mark", scaleFirst: true }),
18
+ encoding: Object.freeze({ scaleFirst: true }),
19
+ scaleApplication: Object.freeze({
20
+ deferWithMark: true,
21
+ position: "rematerialize",
22
+ deferredChannels: Object.freeze(["size", "shape"]),
23
+ default: "direct"
24
+ }),
25
+ rematerializeIncompleteExisting: true
26
+ }),
27
+ line: Object.freeze({
28
+ canMaterialize: canMaterializeLine,
29
+ op: "rematerializeLineMark",
30
+ positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: false }),
31
+ scaleApplication: Object.freeze({ default: "defer" })
32
+ }),
33
+ area: Object.freeze({
34
+ canMaterialize: canMaterializeArea,
35
+ op: "rematerializeAreaMark",
36
+ positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: true }),
37
+ encoding: Object.freeze({
38
+ sharedChannels: Object.freeze(["color"]),
39
+ skipRematerialization: isIntentionallyEmptyArea
40
+ }),
41
+ scaleApplication: Object.freeze({ default: "defer" })
42
+ }),
43
+ arc: Object.freeze({
44
+ canMaterialize: canMaterializeArc,
45
+ op: "rematerializeArcMark",
46
+ positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: false }),
47
+ encoding: Object.freeze({ completeOnly: true }),
48
+ scaleApplication: Object.freeze({ default: "defer" })
49
+ }),
50
+ bar: Object.freeze({
51
+ canMaterialize: canMaterializeBar,
52
+ op: "rematerializeBarMark",
53
+ positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: false }),
54
+ scaleApplication: Object.freeze({ default: "defer" })
55
+ }),
56
+ rule: Object.freeze({
57
+ canMaterialize: canMaterializeRule,
58
+ op: "rematerializeRuleMark",
59
+ positionEncoding: Object.freeze({ incomplete: "mark", scaleFirst: false }),
60
+ scaleApplication: Object.freeze({ default: "defer" })
61
+ }),
62
+ text: Object.freeze({
63
+ canMaterialize: canMaterializeText,
64
+ op: "rematerializeTextMark",
65
+ positionEncoding: Object.freeze({ incomplete: "mark", scaleFirst: true }),
66
+ encoding: Object.freeze({ scaleFirst: true }),
67
+ scaleApplication: Object.freeze({
68
+ deferWithMark: true,
69
+ position: "rematerialize",
70
+ default: "defer"
71
+ }),
72
+ rematerializeIncompleteExisting: true,
73
+ sourceDependent: true
74
+ }),
75
+ rect: Object.freeze({
76
+ canMaterialize: canMaterializeRect,
77
+ op: "rematerializeRectMark",
78
+ positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: true }),
79
+ encoding: Object.freeze({ scaleFirst: true }),
80
+ scaleApplication: Object.freeze({
81
+ deferWithMark: true,
82
+ position: "rematerialize",
83
+ default: "defer"
84
+ }),
85
+ rematerializeIncompleteExisting: true
86
+ })
87
+ });
88
+
89
+ export function getMarkMaterializationPolicy(layer) {
90
+ return MARK_MATERIALIZATION_POLICIES[layer.mark?.type];
91
+ }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  mapOrdinalPositionValues,
3
3
  readScaleField
4
- } from "../grammar/scales.js";
4
+ } from "../grammar/scales/index.js";
5
5
  import { RECT_MODES, resolveRectMode } from "../grammar/rects.js";
6
6
  import { DEFAULT_RECT_MARK } from "./rectConfig.js";
7
7
  import { mapScaleConsumerValues } from "./scales/map.js";
@@ -5,7 +5,7 @@ import {
5
5
  readQuantitativeField,
6
6
  readScaleField,
7
7
  readTemporalField
8
- } from "../grammar/scales.js";
8
+ } from "../grammar/scales/index.js";
9
9
  import { mapScaleConsumerValues } from "./scales/map.js";
10
10
 
11
11
  export function resolveRowEncodingValues(program, layer, dataset, channel) {