ggaction 0.0.2 → 0.0.3

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 (199) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +12 -1
  3. package/package.json +6 -1
  4. package/src/actions/boxPlots/components.js +3 -3
  5. package/src/actions/boxPlots/edit.js +277 -0
  6. package/src/actions/boxPlots/index.js +2 -0
  7. package/src/actions/boxPlots/materialize.js +12 -1
  8. package/src/actions/boxPlots/options.js +25 -14
  9. package/src/actions/canvas/actions.js +21 -17
  10. package/src/actions/composition/actions.js +247 -0
  11. package/src/actions/composition/index.js +1 -0
  12. package/src/actions/coordinates/actions.js +2 -5
  13. package/src/actions/data/density.js +7 -1
  14. package/src/actions/data/filter.js +3 -1
  15. package/src/actions/encodings/appearance.js +11 -0
  16. package/src/actions/encodings/barWidth.js +5 -3
  17. package/src/actions/encodings/color.js +120 -25
  18. package/src/actions/encodings/index.js +2 -0
  19. package/src/actions/encodings/offset.js +90 -72
  20. package/src/actions/encodings/position/apply.js +66 -0
  21. package/src/actions/encodings/position/policies/arc.js +26 -0
  22. package/src/actions/encodings/position/policies/bar.js +9 -5
  23. package/src/actions/encodings/position/policies/index.js +5 -1
  24. package/src/actions/encodings/position/policies/line.js +20 -3
  25. package/src/actions/encodings/position/resolve.js +57 -8
  26. package/src/actions/encodings/position.js +44 -90
  27. package/src/actions/encodings/ranged.js +28 -6
  28. package/src/actions/encodings/shared.js +17 -21
  29. package/src/actions/encodings/text.js +73 -0
  30. package/src/actions/errorBands/create.js +37 -29
  31. package/src/actions/errorBands/edit.js +206 -0
  32. package/src/actions/errorBands/index.js +9 -0
  33. package/src/actions/errorBands/options.js +39 -0
  34. package/src/actions/errorBars/create.js +50 -43
  35. package/src/actions/errorBars/edit.js +130 -0
  36. package/src/actions/errorBars/index.js +3 -0
  37. package/src/actions/errorBars/options.js +52 -0
  38. package/src/actions/facets/actions.js +162 -0
  39. package/src/actions/facets/derive.js +200 -0
  40. package/src/actions/facets/guides.js +16 -0
  41. package/src/actions/facets/index.js +1 -0
  42. package/src/actions/facets/replay.js +69 -0
  43. package/src/actions/guides/applicability.js +113 -0
  44. package/src/actions/guides/axes/axes.js +70 -16
  45. package/src/actions/guides/axes/edit.js +126 -0
  46. package/src/actions/guides/axes/index.js +12 -0
  47. package/src/actions/guides/axes/labels.js +21 -18
  48. package/src/actions/guides/axes/remove.js +60 -0
  49. package/src/actions/guides/axes/tickGroups.js +7 -5
  50. package/src/actions/guides/axes/ticks.js +9 -4
  51. package/src/actions/guides/axes/titles.js +7 -5
  52. package/src/actions/guides/grids/grid.js +144 -15
  53. package/src/actions/guides/grids/resolve.js +5 -13
  54. package/src/actions/guides/guides.js +17 -75
  55. package/src/actions/guides/index.js +6 -0
  56. package/src/actions/guides/legends/categorical/actions.js +48 -9
  57. package/src/actions/guides/legends/categorical/components.js +16 -16
  58. package/src/actions/guides/legends/categorical/index.js +14 -14
  59. package/src/actions/guides/legends/categorical/layout.js +3 -1
  60. package/src/actions/guides/legends/categorical/options.js +2 -5
  61. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  62. package/src/actions/guides/legends/categorical/symbols.js +14 -14
  63. package/src/actions/guides/legends/continuous/common.js +7 -7
  64. package/src/actions/guides/legends/continuous/interval.js +5 -5
  65. package/src/actions/guides/legends/edit.js +2 -6
  66. package/src/actions/guides/legends/focused.js +63 -0
  67. package/src/actions/guides/legends/index.js +4 -0
  68. package/src/actions/guides/legends/remove.js +64 -0
  69. package/src/actions/guides/legends/size.js +12 -14
  70. package/src/actions/guides/polar/axes/facade.js +219 -0
  71. package/src/actions/guides/polar/axes/labels.js +188 -0
  72. package/src/actions/guides/polar/axes/lines.js +113 -0
  73. package/src/actions/guides/polar/axes/shared.js +112 -0
  74. package/src/actions/guides/polar/axes/ticks.js +164 -0
  75. package/src/actions/guides/polar/axes/titles.js +165 -0
  76. package/src/actions/guides/polar/axes.js +55 -0
  77. package/src/actions/guides/polar/grids.js +252 -0
  78. package/src/actions/guides/polar/index.js +2 -0
  79. package/src/actions/guides/polar/resolve.js +269 -0
  80. package/src/actions/index.js +4 -0
  81. package/src/actions/marks/arc/actions.js +239 -0
  82. package/src/actions/marks/arc/index.js +1 -0
  83. package/src/actions/marks/{area.js → area/actions.js} +51 -38
  84. package/src/actions/marks/area/index.js +4 -0
  85. package/src/actions/marks/bar/create.js +26 -5
  86. package/src/actions/marks/bar/materialize.js +25 -31
  87. package/src/actions/marks/index.js +12 -4
  88. package/src/actions/marks/lifecycle.js +27 -0
  89. package/src/actions/marks/line/actions.js +373 -0
  90. package/src/actions/marks/line/index.js +1 -0
  91. package/src/actions/marks/{point.js → point/actions.js} +67 -108
  92. package/src/actions/marks/point/index.js +1 -0
  93. package/src/actions/marks/rect/actions.js +165 -0
  94. package/src/actions/marks/rect/index.js +1 -0
  95. package/src/actions/marks/remove.js +214 -0
  96. package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
  97. package/src/actions/marks/rule/index.js +1 -0
  98. package/src/actions/marks/shared.js +156 -25
  99. package/src/actions/marks/text/actions.js +192 -0
  100. package/src/actions/marks/text/index.js +1 -0
  101. package/src/actions/primitives/createGraphics.js +13 -6
  102. package/src/actions/primitives/editGraphics.js +10 -3
  103. package/src/actions/primitives/semantic.js +26 -3
  104. package/src/actions/primitives/semanticValue.js +25 -3
  105. package/src/actions/regression/components.js +1 -1
  106. package/src/actions/regression/create.js +18 -1
  107. package/src/actions/regression/edit.js +247 -0
  108. package/src/actions/regression/index.js +3 -0
  109. package/src/actions/regression/resolve.js +3 -1
  110. package/src/actions/scales/consumers.js +15 -6
  111. package/src/actions/scales/definitions.js +13 -4
  112. package/src/actions/scales/edit.js +42 -19
  113. package/src/actions/scales/materialize.js +57 -408
  114. package/src/actions/selection/actions.js +38 -24
  115. package/src/actions/selection/index.js +2 -0
  116. package/src/actions/titles/actions.js +63 -2
  117. package/src/actions/titles/index.js +2 -0
  118. package/src/actions/titles/resolve.js +12 -17
  119. package/src/composition.js +10 -0
  120. package/src/core/ChartProgram.js +200 -2
  121. package/src/core/action.js +10 -0
  122. package/src/core/validation.js +16 -0
  123. package/src/core/vocabulary.js +154 -4
  124. package/src/grammar/arcs.js +154 -0
  125. package/src/grammar/bars/aggregate.js +2 -2
  126. package/src/grammar/bars/geometry.js +3 -3
  127. package/src/grammar/bars/policy.js +7 -1
  128. package/src/grammar/coordinates.js +3 -7
  129. package/src/grammar/density.js +20 -1
  130. package/src/grammar/facets/dependencies.js +185 -0
  131. package/src/grammar/facets/guides.js +257 -0
  132. package/src/grammar/facets/scales.js +254 -0
  133. package/src/grammar/facets.js +122 -0
  134. package/src/grammar/lineSeries.js +111 -3
  135. package/src/grammar/polar.js +102 -0
  136. package/src/grammar/polarGuides.js +243 -0
  137. package/src/grammar/polarLineCommands.js +43 -0
  138. package/src/grammar/polarPaths.js +161 -0
  139. package/src/grammar/positionCompatibility.js +18 -2
  140. package/src/grammar/rects.js +26 -0
  141. package/src/grammar/scales/continuous.js +6 -1
  142. package/src/grammar/scales/fields.js +7 -0
  143. package/src/grammar/scales/ordinal.js +5 -2
  144. package/src/grammar/scales/policies.js +1 -1
  145. package/src/grammar/schemas/graphic.js +1 -1
  146. package/src/grammar/schemas/graphicBounds.js +64 -9
  147. package/src/grammar/schemas/graphicTree.js +44 -10
  148. package/src/grammar/schemas/semanticPath.js +31 -9
  149. package/src/grammar/seriesLayout.js +13 -2
  150. package/src/grammar/text.js +71 -0
  151. package/src/grammar/ticks.js +50 -13
  152. package/src/index.js +1 -0
  153. package/src/layout/composition.js +310 -0
  154. package/src/layout/facets.js +131 -0
  155. package/src/layout/title.js +6 -0
  156. package/src/materialization/bars/aggregate.js +5 -2
  157. package/src/materialization/bars/grouped.js +75 -37
  158. package/src/materialization/composition.js +152 -0
  159. package/src/materialization/compositionSnapshot.js +114 -0
  160. package/src/materialization/dependencies.js +71 -16
  161. package/src/materialization/encodings.js +14 -28
  162. package/src/materialization/facetGuides.js +426 -0
  163. package/src/materialization/facets.js +214 -0
  164. package/src/materialization/graphicHierarchy.js +15 -5
  165. package/src/materialization/guides/resources.js +46 -0
  166. package/src/materialization/marks.js +220 -14
  167. package/src/materialization/planner.js +105 -12
  168. package/src/materialization/rect.js +138 -0
  169. package/src/materialization/rectConfig.js +38 -0
  170. package/src/materialization/rowEncoding.js +33 -0
  171. package/src/materialization/scales/map.js +40 -0
  172. package/src/materialization/scales/policies/arc.js +37 -0
  173. package/src/materialization/scales/policies/bar.js +132 -0
  174. package/src/materialization/scales/policies/series.js +76 -0
  175. package/src/materialization/scales/resolve.js +326 -0
  176. package/src/materialization/selection/items/arc.js +29 -0
  177. package/src/materialization/selection/items/bar.js +255 -0
  178. package/src/materialization/selection/items/common.js +131 -0
  179. package/src/materialization/selection/items/index.js +6 -0
  180. package/src/materialization/selection/items/path.js +59 -0
  181. package/src/materialization/selection/items/point.js +55 -0
  182. package/src/materialization/selection/items/rect.js +23 -0
  183. package/src/materialization/selection/items/rule.js +25 -0
  184. package/src/materialization/selection/policies/arc.js +10 -0
  185. package/src/materialization/selection/policies/area.js +1 -1
  186. package/src/materialization/selection/policies/bar.js +1 -1
  187. package/src/materialization/selection/policies/index.js +5 -1
  188. package/src/materialization/selection/policies/line.js +1 -1
  189. package/src/materialization/selection/policies/point.js +1 -1
  190. package/src/materialization/selection/policies/rect.js +10 -0
  191. package/src/materialization/selection/policies/rule.js +1 -1
  192. package/src/materialization/selection/styles.js +24 -16
  193. package/src/materialization/text.js +156 -0
  194. package/src/renderers/canvas/index.js +68 -22
  195. package/types/extension.d.ts +1 -0
  196. package/types/index.d.ts +56 -1
  197. package/types/program.d.ts +664 -27
  198. package/src/actions/marks/line.js +0 -248
  199. package/src/materialization/selection/items.js +0 -503
@@ -80,30 +80,30 @@ export function normalizePointHighlightStyle(args) {
80
80
  };
81
81
  }
82
82
 
83
- export function normalizeBarHighlightStyle(args) {
83
+ function normalizeRectangularHighlightStyle(args, mark) {
84
84
  for (const option of ["shape", "size", "offset", "strokeDash"]) {
85
85
  if (args[option] !== undefined) {
86
- throw new Error(`Bar highlight does not support ${option}.`);
86
+ throw new Error(`${mark} highlight does not support ${option}.`);
87
87
  }
88
88
  }
89
89
  if (args.color !== undefined && args.fill !== undefined) {
90
- throw new Error("Bar highlight accepts color or fill, not both.");
90
+ throw new Error(`${mark} highlight accepts color or fill, not both.`);
91
91
  }
92
92
  const fill = validateNonEmptyString(
93
93
  args.fill ?? args.color ?? DEFAULT_COLORS.highlight,
94
- "Bar highlight fill"
94
+ `${mark} highlight fill`
95
95
  );
96
96
  const opacity = args.opacity === undefined
97
97
  ? undefined
98
- : validateUnitInterval(args.opacity, "Bar highlight opacity");
98
+ : validateUnitInterval(args.opacity, `${mark} highlight opacity`);
99
99
  const stroke = args.stroke === undefined
100
100
  ? undefined
101
- : validateNonEmptyString(args.stroke, "Bar highlight stroke");
101
+ : validateNonEmptyString(args.stroke, `${mark} highlight stroke`);
102
102
  const strokeWidth = args.strokeWidth === undefined
103
103
  ? undefined
104
- : validateNonNegativeFinite(args.strokeWidth, "Bar highlight strokeWidth");
104
+ : validateNonNegativeFinite(args.strokeWidth, `${mark} highlight strokeWidth`);
105
105
  if (strokeWidth !== undefined && stroke === undefined) {
106
- throw new Error("Bar highlight strokeWidth requires stroke.");
106
+ throw new Error(`${mark} highlight strokeWidth requires stroke.`);
107
107
  }
108
108
  return {
109
109
  fill,
@@ -113,6 +113,14 @@ export function normalizeBarHighlightStyle(args) {
113
113
  };
114
114
  }
115
115
 
116
+ export function normalizeBarHighlightStyle(args) {
117
+ return normalizeRectangularHighlightStyle(args, "Bar");
118
+ }
119
+
120
+ export function normalizeRectHighlightStyle(args) {
121
+ return normalizeRectangularHighlightStyle(args, "Rect");
122
+ }
123
+
116
124
  function rejectHighlightOptions(args, mark, options) {
117
125
  for (const option of options) {
118
126
  if (args[option] !== undefined) {
@@ -154,28 +162,28 @@ export function normalizeStrokeHighlightStyle(args, mark) {
154
162
  };
155
163
  }
156
164
 
157
- export function normalizeAreaHighlightStyle(args) {
158
- rejectHighlightOptions(args, "Area", ["shape", "size", "strokeDash"]);
165
+ export function normalizeAreaHighlightStyle(args, mark = "Area") {
166
+ rejectHighlightOptions(args, mark, ["shape", "size", "strokeDash"]);
159
167
  if (args.color !== undefined && args.fill !== undefined) {
160
- throw new Error("Area highlight accepts color or fill, not both.");
168
+ throw new Error(`${mark} highlight accepts color or fill, not both.`);
161
169
  }
162
170
  const stroke = args.stroke === undefined
163
171
  ? undefined
164
- : validateNonEmptyString(args.stroke, "Area highlight stroke");
172
+ : validateNonEmptyString(args.stroke, `${mark} highlight stroke`);
165
173
  const strokeWidth = args.strokeWidth === undefined
166
174
  ? undefined
167
- : validateNonNegativeFinite(args.strokeWidth, "Area highlight strokeWidth");
175
+ : validateNonNegativeFinite(args.strokeWidth, `${mark} highlight strokeWidth`);
168
176
  if (strokeWidth !== undefined && stroke === undefined) {
169
- throw new Error("Area highlight strokeWidth requires stroke.");
177
+ throw new Error(`${mark} highlight strokeWidth requires stroke.`);
170
178
  }
171
179
  return {
172
180
  fill: validateNonEmptyString(
173
181
  args.fill ?? args.color ?? DEFAULT_COLORS.highlight,
174
- "Area highlight fill"
182
+ `${mark} highlight fill`
175
183
  ),
176
184
  ...(args.opacity === undefined
177
185
  ? {}
178
- : { opacity: validateUnitInterval(args.opacity, "Area highlight opacity") }),
186
+ : { opacity: validateUnitInterval(args.opacity, `${mark} highlight opacity`) }),
179
187
  ...(stroke === undefined ? {} : { stroke }),
180
188
  ...(strokeWidth === undefined ? {} : { strokeWidth }),
181
189
  offset: normalizeOffset(args.offset)
@@ -0,0 +1,156 @@
1
+ import { resolveBarChannels } from "../grammar/bars/policy.js";
2
+ import { formatTextValue } from "../grammar/text.js";
3
+ import { findDataset } from "../selectors/datasets.js";
4
+ import { findLayer } from "../selectors/layers.js";
5
+ import { resolveMarkItems } from "./selection/policies/index.js";
6
+ import { mapScaleConsumerValues } from "./scales/map.js";
7
+ import { resolveRowEncodingValues } from "./rowEncoding.js";
8
+
9
+ function sourceValue(item, source, field) {
10
+ if (Object.hasOwn(item.fields, field)) return item.fields[field];
11
+ for (const [channel, encoding] of Object.entries(source.encoding ?? {})) {
12
+ if (encoding?.field !== field || item.channels[channel] === undefined) continue;
13
+ if (source.mark.type === "bar" && item.channels[`${channel}2`] !== undefined) {
14
+ return item.channels[`${channel}2`];
15
+ }
16
+ return item.channels[channel];
17
+ }
18
+ if (item.members.length === 1 && Object.hasOwn(item.members[0], field)) {
19
+ return item.members[0][field];
20
+ }
21
+ return undefined;
22
+ }
23
+
24
+ function barAnchor(program, source, item) {
25
+ const channels = resolveBarChannels(source);
26
+ const measure = channels.measure;
27
+ const endpoint = item.channels[`${measure}2`];
28
+ const scale = program.resolvedScales[source.encoding[measure].scale];
29
+ const mapped = endpoint === undefined || scale === undefined
30
+ ? undefined
31
+ : mapScaleConsumerValues([endpoint], scale, measure)[0];
32
+ return measure === "x"
33
+ ? {
34
+ x: mapped ?? item.properties.x + item.properties.width,
35
+ y: item.properties.y + item.properties.height / 2
36
+ }
37
+ : {
38
+ x: item.properties.x + item.properties.width / 2,
39
+ y: mapped ?? item.properties.y
40
+ };
41
+ }
42
+
43
+ function sourceAnchor(program, source, item) {
44
+ if (source.mark.type === "bar") return barAnchor(program, source, item);
45
+ if (source.mark.type === "rect") {
46
+ return {
47
+ x: item.properties.x + item.properties.width / 2,
48
+ y: item.properties.y + item.properties.height / 2
49
+ };
50
+ }
51
+ if (source.mark.type === "rule") {
52
+ return {
53
+ x: item.properties.x2,
54
+ y: item.properties.y2
55
+ };
56
+ }
57
+ return { x: item.properties.x, y: item.properties.y };
58
+ }
59
+
60
+ function contentValue(encoding, rowOrItem, source) {
61
+ return Object.hasOwn(encoding, "field")
62
+ ? source === undefined
63
+ ? rowOrItem[encoding.field]
64
+ : sourceValue(rowOrItem, source, encoding.field)
65
+ : encoding.datum;
66
+ }
67
+
68
+ function concreteItem(config, position, value, format) {
69
+ const text = formatTextValue(value, format);
70
+ const x = position.x;
71
+ const y = position.y;
72
+ if (text === undefined || !Number.isFinite(x) || !Number.isFinite(y)) {
73
+ return undefined;
74
+ }
75
+ return {
76
+ type: "text",
77
+ properties: {
78
+ x: x + config.dx,
79
+ y: y + config.dy,
80
+ text,
81
+ fill: config.fill,
82
+ opacity: config.opacity,
83
+ fontSize: config.fontSize,
84
+ fontFamily: config.fontFamily,
85
+ fontWeight: config.fontWeight,
86
+ textAlign: config.align,
87
+ textBaseline: config.baseline,
88
+ rotation: config.rotation
89
+ }
90
+ };
91
+ }
92
+
93
+ function relativeLuminance(color) {
94
+ if (typeof color !== "string" || !/^#[0-9a-f]{6}$/i.test(color)) {
95
+ return undefined;
96
+ }
97
+ const channels = [1, 3, 5].map(offset =>
98
+ Number.parseInt(color.slice(offset, offset + 2), 16) / 255
99
+ ).map(value => value <= 0.04045
100
+ ? value / 12.92
101
+ : ((value + 0.055) / 1.055) ** 2.4
102
+ );
103
+ return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
104
+ }
105
+
106
+ function sourceTextConfig(config, source, item) {
107
+ if (source.mark.type !== "rect" || config.fillExplicit === true) return config;
108
+ const luminance = relativeLuminance(item.properties.fill);
109
+ if (luminance === undefined) return config;
110
+ return {
111
+ ...config,
112
+ fill: luminance >= 0.26 ? "#0f172a" : "#f8fafc"
113
+ };
114
+ }
115
+
116
+ function resolveSourceTextItems(program, layer, config) {
117
+ const source = findLayer(program, layer.source);
118
+ if (source === undefined) {
119
+ throw new Error(`Text mark "${layer.id}" requires source layer "${layer.source}".`);
120
+ }
121
+ const items = resolveMarkItems(program, source.id);
122
+ return items.flatMap(item => {
123
+ const concrete = concreteItem(
124
+ sourceTextConfig(config, source, item),
125
+ sourceAnchor(program, source, item),
126
+ contentValue(layer.encoding.text, item, source),
127
+ layer.encoding.text.format
128
+ );
129
+ return concrete === undefined ? [] : [concrete];
130
+ });
131
+ }
132
+
133
+ function resolveRowTextItems(program, layer, config) {
134
+ const dataset = findDataset(program, layer.data);
135
+ if (dataset === undefined) {
136
+ throw new Error(`Text mark "${layer.id}" requires an existing dataset.`);
137
+ }
138
+ const x = resolveRowEncodingValues(program, layer, dataset, "x");
139
+ const y = resolveRowEncodingValues(program, layer, dataset, "y");
140
+ if (x === undefined || y === undefined) return [];
141
+ return dataset.values.flatMap((row, index) => {
142
+ const concrete = concreteItem(
143
+ config,
144
+ { x: x[index], y: y[index] },
145
+ contentValue(layer.encoding.text, row),
146
+ layer.encoding.text.format
147
+ );
148
+ return concrete === undefined ? [] : [concrete];
149
+ });
150
+ }
151
+
152
+ export function resolveTextGraphicItems(program, layer, config) {
153
+ return layer.source === undefined
154
+ ? resolveRowTextItems(program, layer, config)
155
+ : resolveSourceTextItems(program, layer, config);
156
+ }
@@ -4,7 +4,10 @@ import { drawPathGraphic } from "./path.js";
4
4
  import { drawRectGraphic } from "./rect.js";
5
5
  import { drawTextGraphic } from "./text.js";
6
6
  import { requireFiniteProperty } from "./validation.js";
7
- import { walkGraphicDrawOrder } from "../../grammar/schemas/graphicTree.js";
7
+ import {
8
+ requireSingleOrderedGraphicByType,
9
+ walkGraphicTreeEvents
10
+ } from "../../grammar/schemas/graphicTree.js";
8
11
 
9
12
  const DRAWERS = Object.freeze({
10
13
  circle: drawCircleGraphic,
@@ -46,16 +49,41 @@ function requireCanvasContext(context) {
46
49
  }
47
50
  }
48
51
 
49
- function findCanvas(graphicSpec) {
50
- const canvasIds = graphicSpec.order.filter(
51
- id => graphicSpec.objects[id]?.type === "canvas"
52
- );
52
+ function requireContextMethod(context, method, label) {
53
+ if (typeof context[method] !== "function") {
54
+ throw new TypeError(`${label} requires Canvas context ${method}().`);
55
+ }
56
+ }
53
57
 
54
- if (canvasIds.length !== 1) {
55
- throw new Error("graphicSpec must contain exactly one ordered canvas.");
58
+ function fillCanvasBackground(context, id, canvas, width, height) {
59
+ if (canvas.properties.background === undefined) return;
60
+ if (typeof canvas.properties.background !== "string") {
61
+ throw new Error(
62
+ `Graphic "${id}" requires a string background property.`
63
+ );
56
64
  }
65
+ context.globalAlpha = 1;
66
+ context.fillStyle = canvas.properties.background;
67
+ context.fillRect(0, 0, width, height);
68
+ }
57
69
 
58
- return { id: canvasIds[0], graphic: graphicSpec.objects[canvasIds[0]] };
70
+ function enterNestedCanvas(context, id, canvas) {
71
+ const properties = canvas.properties ?? {};
72
+ const x = requireFiniteProperty(properties, "x", id);
73
+ const y = requireFiniteProperty(properties, "y", id);
74
+ const width = requireFiniteProperty(properties, "width", id);
75
+ const height = requireFiniteProperty(properties, "height", id);
76
+ if (width < 0 || height < 0) {
77
+ throw new Error(`Nested Canvas "${id}" width and height must not be negative.`);
78
+ }
79
+ requireContextMethod(context, "rect", `Nested Canvas "${id}"`);
80
+ requireContextMethod(context, "clip", `Nested Canvas "${id}"`);
81
+ context.save();
82
+ context.translate(x, y);
83
+ context.beginPath();
84
+ context.rect(0, 0, width, height);
85
+ context.clip();
86
+ fillCanvasBackground(context, id, canvas, width, height);
59
87
  }
60
88
 
61
89
  export function render(program, context, { pixelRatio = 1 } = {}) {
@@ -77,7 +105,8 @@ export function render(program, context, { pixelRatio = 1 } = {}) {
77
105
  throw new RangeError("render pixelRatio must be a positive finite number.");
78
106
  }
79
107
 
80
- const { id: canvasId, graphic: canvas } = findCanvas(graphicSpec);
108
+ const { id: canvasId, object: canvas } =
109
+ requireSingleOrderedGraphicByType(graphicSpec, "canvas");
81
110
  const width = requireFiniteProperty(canvas.properties ?? {}, "width", canvasId);
82
111
  const height = requireFiniteProperty(
83
112
  canvas.properties ?? {},
@@ -97,20 +126,37 @@ export function render(program, context, { pixelRatio = 1 } = {}) {
97
126
  context.scale(pixelRatio, pixelRatio);
98
127
  context.clearRect(0, 0, width, height);
99
128
 
100
- if (canvas.properties.background !== undefined) {
101
- if (typeof canvas.properties.background !== "string") {
102
- throw new Error(
103
- `Graphic "${canvasId}" requires a string background property.`
104
- );
129
+ fillCanvasBackground(context, canvasId, canvas, width, height);
130
+
131
+ walkGraphicTreeEvents(graphicSpec, {
132
+ enter({ id, object }) {
133
+ if (id === canvasId) return;
134
+ if (object.type === "canvas") {
135
+ enterNestedCanvas(context, id, object);
136
+ return;
137
+ }
138
+ if (object.type === "collection") {
139
+ context.save();
140
+ return;
141
+ }
142
+ if (!Array.isArray(object.items)) {
143
+ drawConcreteGraphic(context, id, object);
144
+ }
145
+ },
146
+ item({ id, object, owner }) {
147
+ drawConcreteGraphic(context, id, {
148
+ ...object,
149
+ type: object.type ?? owner.type
150
+ });
151
+ },
152
+ exit({ id, object }) {
153
+ if (
154
+ id !== canvasId &&
155
+ (object.type === "collection" || object.type === "canvas")
156
+ ) {
157
+ context.restore();
158
+ }
105
159
  }
106
-
107
- context.globalAlpha = 1;
108
- context.fillStyle = canvas.properties.background;
109
- context.fillRect(0, 0, width, height);
110
- }
111
-
112
- walkGraphicDrawOrder(graphicSpec, ({ id, object }) => {
113
- if (id !== canvasId) drawConcreteGraphic(context, id, object);
114
160
  });
115
161
  } finally {
116
162
  context.restore();
@@ -6,6 +6,7 @@ export { ChartProgram };
6
6
  export interface ActionMetadata {
7
7
  op: string;
8
8
  description: string;
9
+ scope?: "unit" | "composition" | "any";
9
10
  }
10
11
 
11
12
  export function action<TOptions extends ActionOptions = ActionOptions>(
package/types/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ChartProgram } from "./program.js";
1
+ import type { ChartProgram, CompositionOptions } from "./program.js";
2
2
 
3
3
  export type {
4
4
  ActionOptions,
@@ -18,19 +18,56 @@ export type {
18
18
  ColorLayout,
19
19
  ColorEncodingOptions,
20
20
  CompleteAxisOptions,
21
+ ConcatCompositionSpec,
22
+ CompositionAlign,
23
+ CompositionOptions,
24
+ CompositionPadding,
25
+ CompositionProgramEntry,
26
+ CompositionSpec,
21
27
  ChartProgram,
22
28
  ConcretePathCommand,
23
29
  CurveInterpolation,
30
+ CreateDerivedDataOptions,
24
31
  CreateAxesOptions,
32
+ CreateCoordinateOptions,
33
+ CreateGuidesOptions,
25
34
  CreateGridOptions,
35
+ CreateRegressionBandOptions,
36
+ CreateRegressionLineOptions,
37
+ CreateScaleOptions,
26
38
  DashPattern,
27
39
  DashScaleOptions,
28
40
  DashStyle,
41
+ DatasetDensityTransform,
42
+ DatasetFilterTransform,
43
+ DatasetIntervalOutputFields,
44
+ DatasetIntervalTransform,
45
+ DatasetRegressionTransform,
46
+ DatasetScalar,
47
+ DatasetTransform,
29
48
  EditGraphicsOptions,
49
+ EditCompositionLayoutOptions,
50
+ EditFacetHeadersOptions,
51
+ EditAxisOptions,
52
+ EditGridDirectionsOptions,
30
53
  EditGridOptions,
54
+ EditErrorBandBoundaryOptions,
55
+ EditErrorBandOptions,
56
+ EditErrorBarOptions,
57
+ EditBoxPlotOptions,
58
+ EditLegendBorderOptions,
59
+ EditLegendLabelsOptions,
60
+ EditLegendLayoutOptions,
61
+ EditLegendSymbolsOptions,
62
+ EditLegendTitleOptions,
31
63
  EditScaleOptions,
32
64
  EditSemanticOptions,
33
65
  FieldType,
66
+ FacetCompositionSpec,
67
+ FacetGuideOptions,
68
+ FacetOptions,
69
+ FacetScaleResolution,
70
+ FacetScaleResolutions,
34
71
  GraphicObject,
35
72
  GraphicSpec,
36
73
  GraphicType,
@@ -57,23 +94,41 @@ export type {
57
94
  RegressionInterval,
58
95
  RegressionMethod,
59
96
  RegressionOptions,
97
+ RectMarkOptions,
98
+ ReplaceCompositionChildOptions,
99
+ EditRegressionOptions,
100
+ EditRectMarkOptions,
101
+ RemoveAxisOptions,
102
+ RemoveGridOptions,
103
+ RemoveLegendOptions,
104
+ RemoveMarkOptions,
60
105
  ScaleRange,
61
106
  ScaleOptions,
62
107
  ScaleType,
63
108
  StackMode,
109
+ SemanticCoordinate,
110
+ SemanticDataset,
111
+ SemanticLayer,
112
+ SemanticScale,
64
113
  SemanticSpec,
65
114
  SelectMarksOptions,
66
115
  StrokeDashEncodingOptions,
67
116
  TraceNode,
68
117
  TitleOptions,
69
118
  EditTitleOptions,
119
+ EditTextMarkOptions,
70
120
  TitleTextStyleOptions,
121
+ TextEncodingOptions,
122
+ TextFormat,
123
+ TextMarkOptions,
71
124
  XAxisPosition,
72
125
  YAxisPosition,
73
126
  XOffsetEncodingOptions
74
127
  } from "./program.js";
75
128
 
76
129
  export function chart(): ChartProgram;
130
+ export function hconcat(options: CompositionOptions): ChartProgram;
131
+ export function vconcat(options: CompositionOptions): ChartProgram;
77
132
  export function render(
78
133
  program: Pick<ChartProgram, "graphicSpec">,
79
134
  context: CanvasRenderingContext2D,