ggaction 0.0.2 → 0.0.4

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 (242) hide show
  1. package/CHANGELOG.md +55 -2
  2. package/README.md +95 -72
  3. package/package.json +15 -5
  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/data/regression.js +1 -1
  16. package/src/actions/encodings/appearance.js +12 -1
  17. package/src/actions/encodings/barWidth.js +5 -3
  18. package/src/actions/encodings/color/continuous.js +137 -0
  19. package/src/actions/encodings/color/index.js +151 -0
  20. package/src/actions/encodings/color/layout.js +91 -0
  21. package/src/actions/encodings/color/policy.js +88 -0
  22. package/src/actions/encodings/index.js +4 -2
  23. package/src/actions/encodings/offset.js +91 -73
  24. package/src/actions/encodings/position/apply.js +66 -0
  25. package/src/actions/encodings/position/index.js +130 -0
  26. package/src/actions/encodings/position/policies/arc.js +26 -0
  27. package/src/actions/encodings/position/policies/bar.js +9 -5
  28. package/src/actions/encodings/position/policies/index.js +5 -1
  29. package/src/actions/encodings/position/policies/line.js +20 -3
  30. package/src/actions/encodings/position/resolve.js +58 -9
  31. package/src/actions/encodings/ranged.js +30 -8
  32. package/src/actions/encodings/shared.js +17 -21
  33. package/src/actions/encodings/strokeDash.js +1 -1
  34. package/src/actions/encodings/text.js +73 -0
  35. package/src/actions/errorBands/create.js +37 -29
  36. package/src/actions/errorBands/edit.js +206 -0
  37. package/src/actions/errorBands/index.js +9 -0
  38. package/src/actions/errorBands/options.js +39 -0
  39. package/src/actions/errorBars/create.js +50 -43
  40. package/src/actions/errorBars/edit.js +130 -0
  41. package/src/actions/errorBars/index.js +3 -0
  42. package/src/actions/errorBars/options.js +52 -0
  43. package/src/actions/facets/actions.js +162 -0
  44. package/src/actions/facets/derive.js +200 -0
  45. package/src/actions/facets/guides.js +16 -0
  46. package/src/actions/facets/index.js +1 -0
  47. package/src/actions/facets/replay.js +69 -0
  48. package/src/actions/guides/applicability.js +113 -0
  49. package/src/actions/guides/axes/axes.js +70 -16
  50. package/src/actions/guides/axes/edit.js +126 -0
  51. package/src/actions/guides/axes/index.js +12 -0
  52. package/src/actions/guides/axes/labels.js +22 -19
  53. package/src/actions/guides/axes/remove.js +60 -0
  54. package/src/actions/guides/axes/tickGroups.js +7 -5
  55. package/src/actions/guides/axes/ticks.js +10 -5
  56. package/src/actions/guides/axes/titles.js +8 -6
  57. package/src/actions/guides/grids/grid.js +144 -15
  58. package/src/actions/guides/grids/resolve.js +6 -14
  59. package/src/actions/guides/guides.js +17 -75
  60. package/src/actions/guides/index.js +6 -0
  61. package/src/actions/guides/legends/categorical/actions.js +48 -9
  62. package/src/actions/guides/legends/categorical/components.js +16 -16
  63. package/src/actions/guides/legends/categorical/index.js +14 -14
  64. package/src/actions/guides/legends/categorical/layout.js +7 -3
  65. package/src/actions/guides/legends/categorical/options.js +2 -5
  66. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  67. package/src/actions/guides/legends/categorical/symbols.js +14 -14
  68. package/src/actions/guides/legends/continuous/common.js +7 -7
  69. package/src/actions/guides/legends/continuous/interval.js +6 -6
  70. package/src/actions/guides/legends/continuous/opacity.js +1 -1
  71. package/src/actions/guides/legends/edit.js +4 -26
  72. package/src/actions/guides/legends/focused.js +63 -0
  73. package/src/actions/guides/legends/index.js +4 -0
  74. package/src/actions/guides/legends/remove.js +46 -0
  75. package/src/actions/guides/legends/size.js +13 -15
  76. package/src/actions/guides/legends/target.js +25 -0
  77. package/src/actions/guides/polar/axes/facade.js +219 -0
  78. package/src/actions/guides/polar/axes/index.js +55 -0
  79. package/src/actions/guides/polar/axes/labels.js +188 -0
  80. package/src/actions/guides/polar/axes/lines.js +113 -0
  81. package/src/actions/guides/polar/axes/shared.js +112 -0
  82. package/src/actions/guides/polar/axes/ticks.js +164 -0
  83. package/src/actions/guides/polar/axes/titles.js +165 -0
  84. package/src/actions/guides/polar/grids.js +252 -0
  85. package/src/actions/guides/polar/index.js +2 -0
  86. package/src/actions/guides/polar/resolve.js +269 -0
  87. package/src/actions/guides/tickValues.js +1 -1
  88. package/src/actions/index.js +4 -0
  89. package/src/actions/intervals/resolve.js +1 -1
  90. package/src/actions/marks/arc/actions.js +239 -0
  91. package/src/actions/marks/arc/index.js +1 -0
  92. package/src/actions/marks/{area.js → area/actions.js} +51 -38
  93. package/src/actions/marks/area/index.js +4 -0
  94. package/src/actions/marks/bar/create.js +26 -5
  95. package/src/actions/marks/bar/edit.js +1 -1
  96. package/src/actions/marks/bar/materialize.js +25 -31
  97. package/src/actions/marks/index.js +12 -4
  98. package/src/actions/marks/lifecycle.js +27 -0
  99. package/src/actions/marks/line/actions.js +373 -0
  100. package/src/actions/marks/line/index.js +1 -0
  101. package/src/actions/marks/{point.js → point/actions.js} +67 -108
  102. package/src/actions/marks/point/index.js +1 -0
  103. package/src/actions/marks/rect/actions.js +165 -0
  104. package/src/actions/marks/rect/index.js +1 -0
  105. package/src/actions/marks/remove.js +214 -0
  106. package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
  107. package/src/actions/marks/rule/index.js +1 -0
  108. package/src/actions/marks/shared.js +156 -25
  109. package/src/actions/marks/text/actions.js +192 -0
  110. package/src/actions/marks/text/index.js +1 -0
  111. package/src/actions/primitives/createGraphics.js +13 -6
  112. package/src/actions/primitives/editGraphics.js +10 -3
  113. package/src/actions/primitives/semantic.js +26 -3
  114. package/src/actions/primitives/semanticValue.js +26 -4
  115. package/src/actions/regression/components.js +2 -2
  116. package/src/actions/regression/create.js +19 -2
  117. package/src/actions/regression/edit.js +247 -0
  118. package/src/actions/regression/index.js +3 -0
  119. package/src/actions/regression/resolve.js +3 -1
  120. package/src/actions/scales/consumers/common.js +91 -0
  121. package/src/actions/scales/consumers/families.js +47 -0
  122. package/src/actions/scales/consumers/index.js +41 -0
  123. package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +14 -127
  124. package/src/actions/scales/create.js +1 -1
  125. package/src/actions/scales/definitions.js +15 -6
  126. package/src/actions/scales/edit.js +45 -22
  127. package/src/actions/scales/materialize.js +58 -409
  128. package/src/actions/selection/actions.js +38 -24
  129. package/src/actions/selection/index.js +2 -0
  130. package/src/actions/titles/actions.js +63 -2
  131. package/src/actions/titles/index.js +2 -0
  132. package/src/actions/titles/resolve.js +12 -17
  133. package/src/composition.js +10 -0
  134. package/src/core/ChartProgram.js +49 -65
  135. package/src/core/action.js +10 -0
  136. package/src/core/compositionState.js +169 -0
  137. package/src/core/materializationState.js +65 -0
  138. package/src/core/programState.js +28 -0
  139. package/src/core/validation.js +16 -0
  140. package/src/core/vocabulary.js +154 -4
  141. package/src/grammar/arcs.js +154 -0
  142. package/src/grammar/areaSeries.js +1 -1
  143. package/src/grammar/bars/aggregate.js +3 -3
  144. package/src/grammar/bars/geometry.js +3 -3
  145. package/src/grammar/bars/policy.js +7 -1
  146. package/src/grammar/coordinates.js +3 -7
  147. package/src/grammar/density.js +21 -2
  148. package/src/grammar/facets/dependencies.js +185 -0
  149. package/src/grammar/facets/guides.js +257 -0
  150. package/src/grammar/facets/index.js +122 -0
  151. package/src/grammar/facets/scales.js +254 -0
  152. package/src/grammar/interval.js +2 -96
  153. package/src/grammar/lineSeries.js +112 -4
  154. package/src/grammar/palettes.js +5 -7
  155. package/src/grammar/polar.js +102 -0
  156. package/src/grammar/polarGuides.js +243 -0
  157. package/src/grammar/polarLineCommands.js +43 -0
  158. package/src/grammar/polarPaths.js +161 -0
  159. package/src/grammar/positionCompatibility.js +18 -2
  160. package/src/grammar/rects.js +26 -0
  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 +1 -1
  166. package/src/grammar/scales/color.js +4 -2
  167. package/src/grammar/scales/continuous.js +6 -1
  168. package/src/grammar/scales/fields.js +7 -0
  169. package/src/grammar/scales/ordinal.js +5 -2
  170. package/src/grammar/scales/policies.js +1 -1
  171. package/src/grammar/schemas/graphic.js +1 -1
  172. package/src/grammar/schemas/graphicBounds.js +64 -9
  173. package/src/grammar/schemas/graphicTree.js +44 -10
  174. package/src/grammar/schemas/semanticPath.js +31 -9
  175. package/src/grammar/seriesLayout.js +13 -2
  176. package/src/grammar/statistics/studentT.js +98 -0
  177. package/src/grammar/text.js +71 -0
  178. package/src/grammar/ticks.js +50 -13
  179. package/src/grammar/transforms.js +1 -1
  180. package/src/index.js +1 -0
  181. package/src/layout/composition.js +310 -0
  182. package/src/layout/facets.js +131 -0
  183. package/src/layout/title.js +6 -0
  184. package/src/materialization/bars/aggregate.js +6 -3
  185. package/src/materialization/bars/grouped.js +75 -37
  186. package/src/materialization/bars/histogram.js +1 -1
  187. package/src/materialization/bars/ranged.js +1 -1
  188. package/src/materialization/composition.js +152 -0
  189. package/src/materialization/compositionSnapshot.js +114 -0
  190. package/src/materialization/density.js +1 -1
  191. package/src/materialization/dependencies.js +47 -73
  192. package/src/materialization/encodings.js +14 -28
  193. package/src/materialization/facetGuides/index.js +2 -0
  194. package/src/materialization/facetGuides/legacyCategorical.js +125 -0
  195. package/src/materialization/facetGuides/placement.js +126 -0
  196. package/src/materialization/facetGuides/preparation.js +188 -0
  197. package/src/materialization/facets.js +214 -0
  198. package/src/materialization/graphicHierarchy.js +15 -5
  199. package/src/materialization/guides/resources.js +46 -0
  200. package/src/materialization/marks/capabilities.js +141 -0
  201. package/src/materialization/marks/index.js +105 -0
  202. package/src/materialization/marks/policies.js +87 -0
  203. package/src/materialization/planner.js +105 -12
  204. package/src/materialization/rect.js +138 -0
  205. package/src/materialization/rectConfig.js +38 -0
  206. package/src/materialization/rowEncoding.js +33 -0
  207. package/src/materialization/scaleGuideDependencies.js +94 -0
  208. package/src/materialization/scales/map.js +40 -0
  209. package/src/materialization/scales/policies/arc.js +37 -0
  210. package/src/materialization/scales/policies/bar.js +132 -0
  211. package/src/materialization/scales/policies/series.js +76 -0
  212. package/src/materialization/scales/resolve.js +326 -0
  213. package/src/materialization/selection/items/arc.js +29 -0
  214. package/src/materialization/selection/items/bar.js +255 -0
  215. package/src/materialization/selection/items/common.js +131 -0
  216. package/src/materialization/selection/items/index.js +6 -0
  217. package/src/materialization/selection/items/path.js +59 -0
  218. package/src/materialization/selection/items/point.js +55 -0
  219. package/src/materialization/selection/items/rect.js +23 -0
  220. package/src/materialization/selection/items/rule.js +25 -0
  221. package/src/materialization/selection/policies/arc.js +10 -0
  222. package/src/materialization/selection/policies/area.js +1 -1
  223. package/src/materialization/selection/policies/bar.js +1 -1
  224. package/src/materialization/selection/policies/index.js +5 -1
  225. package/src/materialization/selection/policies/line.js +1 -1
  226. package/src/materialization/selection/policies/point.js +1 -1
  227. package/src/materialization/selection/policies/rect.js +10 -0
  228. package/src/materialization/selection/policies/rule.js +1 -1
  229. package/src/materialization/selection/styles.js +25 -17
  230. package/src/materialization/text.js +156 -0
  231. package/src/renderers/canvas/index.js +68 -22
  232. package/src/renderers/canvas/text.js +14 -1
  233. package/types/extension.d.ts +5 -1
  234. package/types/index.d.ts +56 -1
  235. package/types/program.d.ts +664 -27
  236. package/src/actions/encodings/color.js +0 -329
  237. package/src/actions/encodings/position.js +0 -176
  238. package/src/actions/marks/line.js +0 -248
  239. package/src/grammar/regression.js +0 -578
  240. package/src/grammar/scales.js +0 -1
  241. package/src/materialization/marks.js +0 -110
  242. package/src/materialization/selection/items.js +0 -503
@@ -0,0 +1,373 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { deriveLineSeries } from "../../../grammar/lineSeries.js";
3
+ import { mapContinuousScaleValues, mapOrdinalValues } from "../../../grammar/scales/index.js";
4
+ import { validateUserId } from "../../../core/identifiers.js";
5
+ import {
6
+ validateNonEmptyString,
7
+ validateNonNegativeFinite,
8
+ validateUnitInterval
9
+ } from "../../../core/validation.js";
10
+ import {
11
+ assertMarkAvailable,
12
+ applyLayeredMarkInheritance,
13
+ materializeInheritedMark,
14
+ resolveLayeredMarkInheritance,
15
+ resolveMarkId,
16
+ resolveMarkData,
17
+ validateMarkOptions
18
+ } from "../shared.js";
19
+ import { DEFAULT_COLORS } from "../../../theme/defaults.js";
20
+ import { findDataset } from "../../../selectors/datasets.js";
21
+ import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
22
+ import {
23
+ buildCurvePathCommands,
24
+ validateCurveInterpolation
25
+ } from "../../../grammar/curveCommands.js";
26
+ import { buildPolarLinePathCommands } from "../../../grammar/polarLineCommands.js";
27
+ import { resolvePolarFrame } from "../../../grammar/polar.js";
28
+ import { resolveGraphicBounds } from "../../../layout/canvas.js";
29
+ import { normalizeStrokeDashPattern } from "../../../grammar/scales/index.js";
30
+ import { canMaterializeLine } from "../../../materialization/marks/index.js";
31
+ import { resolveMarkGraphicPlacement } from
32
+ "../../../materialization/graphicHierarchy.js";
33
+ import { rematerializeHighlightBaseline } from "../lifecycle.js";
34
+
35
+ const DEFAULT_LINE_STROKE = DEFAULT_COLORS.mark;
36
+ const DEFAULT_LINE_WIDTH = 2;
37
+ const CREATE_OPTIONS = Object.freeze([
38
+ "id", "data", "stroke", "strokeWidth", "opacity", "curve", "closed"
39
+ ]);
40
+ const EDIT_OPTIONS = Object.freeze([
41
+ "target", "stroke", "strokeWidth", "opacity", "curve", "closed"
42
+ ]);
43
+ const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
44
+
45
+ function validateClosed(value) {
46
+ if (typeof value !== "boolean") {
47
+ throw new TypeError("Line closed must be a boolean.");
48
+ }
49
+ return value;
50
+ }
51
+
52
+ function isPolarLine(layer) {
53
+ return layer.encoding?.theta !== undefined ||
54
+ layer.encoding?.radius !== undefined;
55
+ }
56
+
57
+ function validatePolarLineConfig(layer, config) {
58
+ if (!isPolarLine(layer)) return;
59
+ if ((config.curve ?? "linear") !== "linear") {
60
+ throw new Error("Polar line position currently requires curve \"linear\".");
61
+ }
62
+ }
63
+
64
+ const createLineMark = action(
65
+ {
66
+ op: "createLineMark",
67
+ description: "Create a semantic line mark and empty path collection."
68
+ },
69
+ function (args = {}) {
70
+ validateMarkOptions(args, CREATE_OPTIONS, "createLineMark");
71
+ const id = resolveMarkId(this, args.id, {
72
+ defaultId: "line",
73
+ label: "Line mark id",
74
+ markType: "line",
75
+ operation: "createLineMark"
76
+ });
77
+ const inherited = resolveLayeredMarkInheritance(this, args, "line");
78
+ const { data } = resolveMarkData(this, {
79
+ ...args,
80
+ ...(args.data === undefined && this.context.currentData === undefined &&
81
+ inherited?.data !== undefined ? { data: inherited.data } : {})
82
+ });
83
+ const strokeWidth = validateNonNegativeFinite(
84
+ args.strokeWidth ?? DEFAULT_LINE_WIDTH,
85
+ "Line strokeWidth"
86
+ );
87
+ if (args.scales !== undefined && typeof args.scales !== "boolean") {
88
+ throw new TypeError("rematerializeLineMark scales must be a boolean.");
89
+ }
90
+ const curve = validateCurveInterpolation(args.curve ?? "linear");
91
+ const closed = Object.hasOwn(args, "closed")
92
+ ? validateClosed(args.closed)
93
+ : false;
94
+ const stroke = Object.hasOwn(args, "stroke")
95
+ ? validateNonEmptyString(args.stroke, "Line stroke")
96
+ : undefined;
97
+ const opacity = Object.hasOwn(args, "opacity")
98
+ ? validateUnitInterval(args.opacity, "Line opacity")
99
+ : undefined;
100
+ assertMarkAvailable(this, id);
101
+
102
+ let created = this
103
+ .editSemantic({
104
+ property: `layer[${id}].mark.type`,
105
+ value: "line"
106
+ })
107
+ .editSemantic({
108
+ property: `layer[${id}].data`,
109
+ value: data
110
+ });
111
+ created = applyLayeredMarkInheritance(created, id, inherited);
112
+ created = created
113
+ .createGraphics({
114
+ id,
115
+ type: "path",
116
+ length: 0,
117
+ ...resolveMarkGraphicPlacement(created, { data, markType: "line" })
118
+ })
119
+ ._withMarkConfig(
120
+ id,
121
+ {
122
+ ...(Object.hasOwn(args, "strokeWidth") ? { strokeWidth } : {}),
123
+ ...(Object.hasOwn(args, "curve") ? { curve } : {}),
124
+ ...(Object.hasOwn(args, "closed") ? { closed } : {})
125
+ }
126
+ );
127
+ created = materializeInheritedMark(created, id);
128
+ const appearance = {
129
+ ...(stroke === undefined ? {} : { stroke }),
130
+ ...(opacity === undefined ? {} : { opacity })
131
+ };
132
+ return Object.keys(appearance).length === 0
133
+ ? created
134
+ : created.editLineMark({ target: id, ...appearance });
135
+ }
136
+ );
137
+
138
+ function requireLine(program, id) {
139
+ const layer = findLayer(program, id);
140
+
141
+ if (layer?.mark?.type !== "line") {
142
+ throw new Error(`Unknown line mark "${id}".`);
143
+ }
144
+
145
+ const dataset = findDataset(program, layer.data);
146
+
147
+ if (dataset === undefined) {
148
+ throw new Error(`Line mark "${id}" requires an existing dataset.`);
149
+ }
150
+
151
+ if (program.graphicSpec.objects[id]?.type !== "path") {
152
+ throw new Error(`Line mark "${id}" requires path graphics.`);
153
+ }
154
+
155
+ return { dataset, layer };
156
+ }
157
+
158
+ const rematerializeLineMark = action(
159
+ {
160
+ op: "rematerializeLineMark",
161
+ description: "Recompute aggregate series and concrete line paths."
162
+ },
163
+ function (args = {}) {
164
+ validateMarkOptions(
165
+ args,
166
+ REMATERIALIZE_OPTIONS,
167
+ "rematerializeLineMark"
168
+ );
169
+ const id = validateUserId(args.id, "Line mark id");
170
+ const highlighted = rematerializeHighlightBaseline(this, {
171
+ target: id,
172
+ operation: "rematerializeLineMark",
173
+ resetProperty: "length",
174
+ resetValue: 0
175
+ });
176
+ if (highlighted !== undefined) return highlighted;
177
+ const { dataset, layer } = requireLine(this, id);
178
+ const existingChildren = this.graphicSpec.objects[id].items;
179
+ const xScaleId = layer.encoding?.x?.scale;
180
+ const yScaleId = layer.encoding?.y?.scale;
181
+ const thetaScaleId = layer.encoding?.theta?.scale;
182
+ const radiusScaleId = layer.encoding?.radius?.scale;
183
+ const polar = isPolarLine(layer);
184
+ const config = this.markConfigs[id] ?? {};
185
+ validatePolarLineConfig(layer, config);
186
+
187
+ if (
188
+ polar
189
+ ? thetaScaleId === undefined || radiusScaleId === undefined
190
+ : xScaleId === undefined || yScaleId === undefined
191
+ ) {
192
+ throw new Error(
193
+ polar
194
+ ? `Line mark "${id}" requires theta and radius scales.`
195
+ : `Line mark "${id}" requires x and y scales.`
196
+ );
197
+ }
198
+
199
+ let resolved = args.scales === false
200
+ ? this
201
+ : polar
202
+ ? this
203
+ .rematerializeScale({ id: thetaScaleId })
204
+ .rematerializeScale({ id: radiusScaleId })
205
+ : this
206
+ .rematerializeScale({ id: xScaleId })
207
+ .rematerializeScale({ id: yScaleId });
208
+
209
+ for (const channel of args.scales === false ? [] : ["color", "strokeDash"]) {
210
+ const scaleId = layer.encoding?.[channel]?.scale;
211
+ if (scaleId !== undefined) {
212
+ resolved = resolved.rematerializeScale({ id: scaleId });
213
+ }
214
+ }
215
+
216
+ const derived = deriveLineSeries(
217
+ dataset.values,
218
+ layer,
219
+ polar
220
+ ? { thetaDomain: resolved.resolvedScales[thetaScaleId].domain }
221
+ : undefined
222
+ );
223
+ const commands = polar
224
+ ? derived.series.map(series => buildPolarLinePathCommands({
225
+ series: series.values,
226
+ thetaFieldType: derived.thetaFieldType,
227
+ thetaScale: resolved.resolvedScales[thetaScaleId],
228
+ radiusScale: resolved.resolvedScales[radiusScaleId],
229
+ frame: resolvePolarFrame(resolveGraphicBounds(resolved)),
230
+ closed: config.closed ?? false
231
+ }))
232
+ : derived.series.map(series => {
233
+ const x = mapContinuousScaleValues(
234
+ series.values.map(value => value.x),
235
+ resolved.resolvedScales[xScaleId]
236
+ );
237
+ const y = mapContinuousScaleValues(
238
+ series.values.map(value => value.y),
239
+ resolved.resolvedScales[yScaleId]
240
+ );
241
+
242
+ return buildCurvePathCommands(
243
+ series.values.map((_, index) => ({ x: x[index], y: y[index] })),
244
+ config.curve ?? "linear"
245
+ );
246
+ });
247
+ const colorEncoding = layer.encoding?.color;
248
+ const dashEncoding = layer.encoding?.strokeDash;
249
+ const strokes = colorEncoding?.scale === undefined
250
+ ? commands.map(
251
+ (_, index) =>
252
+ config.stroke ??
253
+ existingChildren[index]?.properties.stroke ??
254
+ DEFAULT_LINE_STROKE
255
+ )
256
+ : mapOrdinalValues(
257
+ derived.series.map(series => series.key[colorEncoding.field]),
258
+ resolved.resolvedScales[colorEncoding.scale].domain,
259
+ resolved.resolvedScales[colorEncoding.scale].range
260
+ );
261
+ const strokeWidths = commands.map(
262
+ (_, index) =>
263
+ config.strokeWidth ??
264
+ existingChildren[index]?.properties.strokeWidth ??
265
+ DEFAULT_LINE_WIDTH
266
+ );
267
+ const strokeDashes = dashEncoding?.datum !== undefined
268
+ ? commands.map(() => normalizeStrokeDashPattern(dashEncoding.datum))
269
+ : dashEncoding?.scale === undefined
270
+ ? commands.map(
271
+ (_, index) => existingChildren[index]?.properties.strokeDash ?? []
272
+ )
273
+ : mapOrdinalValues(
274
+ derived.series.map(series => series.key[dashEncoding.field]),
275
+ resolved.resolvedScales[dashEncoding.scale].domain,
276
+ resolved.resolvedScales[dashEncoding.scale].range
277
+ );
278
+
279
+ let next = resolved
280
+ .editGraphics({ target: id, property: "length", value: commands.length })
281
+ .editGraphics({ target: id, property: "commands", value: commands })
282
+ .editGraphics({ target: id, property: "stroke", value: strokes })
283
+ .editGraphics({ target: id, property: "strokeWidth", value: strokeWidths })
284
+ .editGraphics({
285
+ target: id,
286
+ property: "strokeDash",
287
+ value: strokeDashes
288
+ });
289
+ if (config.opacity !== undefined) {
290
+ next = next.editGraphics({
291
+ target: id,
292
+ property: "opacity",
293
+ value: config.opacity
294
+ });
295
+ }
296
+ return next;
297
+ }
298
+ );
299
+
300
+ const editLineMark = action(
301
+ {
302
+ op: "editLineMark",
303
+ description: "Edit line-mark curve and stroke width."
304
+ },
305
+ function (args = {}) {
306
+ validateMarkOptions(args, EDIT_OPTIONS, "editLineMark");
307
+ if (
308
+ !Object.hasOwn(args, "stroke") &&
309
+ !Object.hasOwn(args, "strokeWidth") &&
310
+ !Object.hasOwn(args, "opacity") &&
311
+ !Object.hasOwn(args, "curve") &&
312
+ !Object.hasOwn(args, "closed")
313
+ ) {
314
+ throw new Error(
315
+ "editLineMark requires stroke, strokeWidth, opacity, curve, or closed."
316
+ );
317
+ }
318
+ const target = Object.hasOwn(args, "target")
319
+ ? validateUserId(args.target, "Line mark id")
320
+ : undefined;
321
+ const layer = resolveEligibleLayer(this, {
322
+ target,
323
+ predicate: candidate => candidate.mark?.type === "line",
324
+ label: "line mark"
325
+ });
326
+ if (Object.hasOwn(args, "stroke") && layer.encoding?.color !== undefined) {
327
+ throw new Error(
328
+ "editLineMark stroke cannot be combined with a color encoding."
329
+ );
330
+ }
331
+ if (Object.hasOwn(args, "closed") && args.closed === true &&
332
+ layer.encoding?.x !== undefined) {
333
+ throw new Error("Line closed requires theta/radius Polar position encodings.");
334
+ }
335
+ if (Object.hasOwn(args, "curve") && isPolarLine(layer) &&
336
+ args.curve !== "linear") {
337
+ throw new Error("Polar line position currently requires curve \"linear\".");
338
+ }
339
+ const config = {
340
+ ...this.markConfigs[layer.id],
341
+ ...(Object.hasOwn(args, "stroke")
342
+ ? { stroke: validateNonEmptyString(args.stroke, "Line stroke") }
343
+ : {}),
344
+ ...(Object.hasOwn(args, "strokeWidth")
345
+ ? {
346
+ strokeWidth: validateNonNegativeFinite(
347
+ args.strokeWidth,
348
+ "Line strokeWidth"
349
+ )
350
+ }
351
+ : {}),
352
+ ...(Object.hasOwn(args, "opacity")
353
+ ? { opacity: validateUnitInterval(args.opacity, "Line opacity") }
354
+ : {}),
355
+ ...(Object.hasOwn(args, "curve")
356
+ ? { curve: validateCurveInterpolation(args.curve) }
357
+ : {}),
358
+ ...(Object.hasOwn(args, "closed")
359
+ ? { closed: validateClosed(args.closed) }
360
+ : {})
361
+ };
362
+ const next = this._withMarkConfig(layer.id, config);
363
+ return canMaterializeLine(next, layer)
364
+ ? next.rematerializeLineMark({ id: layer.id })
365
+ : next;
366
+ }
367
+ );
368
+
369
+ export function registerLineMarkActions(ProgramClass) {
370
+ ProgramClass.prototype.createLineMark = createLineMark;
371
+ ProgramClass.prototype.editLineMark = editLineMark;
372
+ ProgramClass.prototype.rematerializeLineMark = rematerializeLineMark;
373
+ }
@@ -0,0 +1 @@
1
+ export { registerLineMarkActions } from "./actions.js";
@@ -1,41 +1,39 @@
1
- import { action } from "../../core/action.js";
2
- import { validateUserId } from "../../core/identifiers.js";
1
+ import { action } from "../../../core/action.js";
2
+ import { validateUserId } from "../../../core/identifiers.js";
3
3
  import {
4
4
  validateNonEmptyString,
5
5
  validateNonNegativeFinite,
6
6
  validateUnitInterval
7
- } from "../../core/validation.js";
8
- import { getPointGraphicType } from "../../grammar/schemas/mark.js";
7
+ } from "../../../core/validation.js";
8
+ import { getPointGraphicType } from "../../../grammar/schemas/mark.js";
9
9
  import {
10
10
  createPointShapeGraphic,
11
11
  validatePointShape
12
- } from "../../grammar/pointShapes.js";
13
- import {
14
- mapContinuousScaleValues,
15
- mapDiscretizedColors,
16
- mapOrdinalPositionValues,
17
- mapOrdinalValues,
18
- mapSequentialColors,
19
- readScaleField,
20
- readNominalField,
21
- readQuantitativeField,
22
- readTemporalField
23
- } from "../../grammar/scales.js";
12
+ } from "../../../grammar/pointShapes.js";
13
+ import { polarToCartesian, resolvePolarFrame } from "../../../grammar/polar.js";
14
+ import { resolveGraphicBounds } from "../../../layout/canvas.js";
24
15
  import {
25
16
  assertMarkAvailable,
17
+ applyLayeredMarkInheritance,
18
+ materializeInheritedMark,
26
19
  resolveLayeredMarkInheritance,
27
20
  resolveMarkId,
28
21
  resolveMarkData,
29
22
  validateMarkOptions
30
- } from "./shared.js";
31
- import { DEFAULT_COLORS } from "../../theme/defaults.js";
32
- import { findDataset } from "../../selectors/datasets.js";
33
- import { findLayer } from "../../selectors/layers.js";
34
- import { rematerializeExistingLegend } from "../encodings/shared.js";
23
+ } from "../shared.js";
24
+ import { DEFAULT_COLORS } from "../../../theme/defaults.js";
25
+ import { findDataset } from "../../../selectors/datasets.js";
26
+ import { findLayer } from "../../../selectors/layers.js";
27
+ import { rematerializeExistingLegend } from "../../encodings/shared.js";
35
28
  import { resolveMarkGraphicPlacement } from
36
- "../../materialization/graphicHierarchy.js";
29
+ "../../../materialization/graphicHierarchy.js";
30
+ import { rematerializeHighlightBaseline } from "../lifecycle.js";
31
+ import { resolveRowEncodingValues } from
32
+ "../../../materialization/rowEncoding.js";
37
33
 
38
- const POINT_MARK_OPTIONS = Object.freeze(["id", "data", "shape"]);
34
+ const POINT_MARK_OPTIONS = Object.freeze([
35
+ "id", "data", "shape", "fill", "opacity", "stroke", "strokeWidth"
36
+ ]);
39
37
  const EDIT_POINT_OPTIONS = Object.freeze([
40
38
  "target", "shape", "fill", "opacity", "stroke", "strokeWidth"
41
39
  ]);
@@ -55,7 +53,7 @@ const createPointMark = action(
55
53
  markType: "point",
56
54
  operation: "createPointMark"
57
55
  });
58
- const inherited = resolveLayeredMarkInheritance(this, args);
56
+ const inherited = resolveLayeredMarkInheritance(this, args, "point");
59
57
  const { data, dataset } = resolveMarkData(this, {
60
58
  ...args,
61
59
  ...(args.data === undefined &&
@@ -80,23 +78,7 @@ const createPointMark = action(
80
78
  property: `layer[${id}].data`,
81
79
  value: data
82
80
  });
83
- if (inherited?.coordinate !== undefined) {
84
- next = next.editSemantic({
85
- property: `layer[${id}].coordinate`,
86
- value: inherited.coordinate
87
- });
88
- }
89
- for (const channel of ["x", "y"]) {
90
- for (const [property, value] of Object.entries(
91
- inherited?.encoding[channel] ?? {}
92
- )) {
93
- next = next.editSemantic({
94
- property: `layer[${id}].encoding.${channel}.${property}`,
95
- value
96
- });
97
- }
98
- }
99
- next = next
81
+ next = applyLayeredMarkInheritance(next, id, inherited)
100
82
  .createGraphics({
101
83
  id,
102
84
  type: graphicType,
@@ -104,56 +86,43 @@ const createPointMark = action(
104
86
  ...resolveMarkGraphicPlacement(next, { data, markType: "point" })
105
87
  })
106
88
  ._withMarkConfig(id, { shape });
107
- const inheritedEncodings = Object.values(inherited?.encoding ?? {});
108
- const canMaterializeInherited = inheritedEncodings.length > 0 &&
109
- inheritedEncodings.every(encoding =>
110
- encoding.scale !== undefined && next.resolvedScales[encoding.scale] !== undefined
111
- );
112
- return !canMaterializeInherited
113
- ? next
114
- : next.rematerializePointMark({ id });
89
+ const created = materializeInheritedMark(next, id);
90
+ const appearance = Object.fromEntries(
91
+ ["fill", "opacity", "stroke", "strokeWidth"]
92
+ .filter(property => Object.hasOwn(args, property))
93
+ .map(property => [property, args[property]])
94
+ );
95
+ return Object.keys(appearance).length === 0
96
+ ? created
97
+ : created.editPointMark({ target: id, ...appearance });
115
98
  }
116
99
  );
117
100
 
118
- function resolveMappedValues(program, layer, dataset, channel) {
119
- const encoding = layer.encoding?.[channel];
120
- if (encoding === undefined) return undefined;
121
- const scale = program.resolvedScales[encoding.scale];
122
- if (scale === undefined) {
123
- throw new Error(
124
- `Point mark "${layer.id}" requires resolved ${channel} scale "${encoding.scale}".`
125
- );
101
+ function resolvePointPositions(program, layer, dataset) {
102
+ const hasPolar = layer.encoding?.theta !== undefined ||
103
+ layer.encoding?.radius !== undefined;
104
+ if (!hasPolar) {
105
+ return {
106
+ x: resolveRowEncodingValues(program, layer, dataset, "x"),
107
+ y: resolveRowEncodingValues(program, layer, dataset, "y")
108
+ };
126
109
  }
127
- const ordinal = ["nominal", "ordinal"].includes(encoding.fieldType);
128
- const values = Object.hasOwn(scale, "unknown")
129
- ? readScaleField(dataset.values, encoding.field, encoding.fieldType, {
130
- allowUnknown: true
131
- })
132
- : ordinal
133
- ? readNominalField(dataset.values, encoding.field)
134
- : encoding.fieldType === "temporal"
135
- ? readTemporalField(dataset.values, encoding.field)
136
- : readQuantitativeField(dataset.values, encoding.field);
137
- if (channel === "color" && scale.type === "sequential") {
138
- return mapSequentialColors(values, scale.domain, scale.range, {
139
- interpolation: scale.interpolate,
140
- clamp: scale.clamp ?? false,
141
- ...(Object.hasOwn(scale, "unknown") ? { unknown: scale.unknown } : {})
142
- });
110
+ const theta = resolveRowEncodingValues(program, layer, dataset, "theta");
111
+ const radius = resolveRowEncodingValues(program, layer, dataset, "radius");
112
+ if (theta === undefined || radius === undefined) {
113
+ return { x: undefined, y: undefined };
143
114
  }
144
- if (
145
- channel === "color" &&
146
- ["quantize", "quantile", "threshold"].includes(scale.type)
147
- ) {
148
- return mapDiscretizedColors(values, scale);
149
- }
150
- return ordinal
151
- ? ["x", "y"].includes(channel)
152
- ? mapOrdinalPositionValues(values, scale)
153
- : mapOrdinalValues(values, scale.domain, scale.range, {
154
- ...(Object.hasOwn(scale, "unknown") ? { unknown: scale.unknown } : {})
155
- })
156
- : mapContinuousScaleValues(values, scale);
115
+ const frame = resolvePolarFrame(resolveGraphicBounds(program));
116
+ const positions = theta.map((angle, index) => {
117
+ const distance = radius[index];
118
+ return Number.isFinite(angle) && Number.isFinite(distance)
119
+ ? polarToCartesian({ theta: angle, radius: distance, frame })
120
+ : { x: undefined, y: undefined };
121
+ });
122
+ return {
123
+ x: positions.map(position => position.x),
124
+ y: positions.map(position => position.y)
125
+ };
157
126
  }
158
127
 
159
128
  function compactProperties(properties) {
@@ -193,22 +162,13 @@ const rematerializePointMark = action(
193
162
  function (args = {}) {
194
163
  validateMarkOptions(args, REMATERIALIZE_OPTIONS, "rematerializePointMark");
195
164
  const id = validateUserId(args.id, "Point mark id");
196
- const highlights = Object.entries(
197
- this.materializationConfigs.highlights ?? {}
198
- ).filter(([, config]) => config.target === id);
199
- if (highlights.length > 0) {
200
- let baseline = this;
201
- for (const [highlightId] of highlights) {
202
- baseline = baseline._withoutMaterializationConfig([
203
- "highlights",
204
- highlightId
205
- ]);
206
- }
207
- return baseline
208
- .editGraphics({ target: id, property: "items", value: [] })
209
- .rematerializePointMark({ id })
210
- .rematerializeMarkHighlights({ target: id, highlights });
211
- }
165
+ const highlighted = rematerializeHighlightBaseline(this, {
166
+ target: id,
167
+ operation: "rematerializePointMark",
168
+ resetProperty: "items",
169
+ resetValue: []
170
+ });
171
+ if (highlighted !== undefined) return highlighted;
212
172
  const layer = findLayer(this, id);
213
173
  const graphic = this.graphicSpec.objects[id];
214
174
 
@@ -223,12 +183,11 @@ const rematerializePointMark = action(
223
183
  throw new Error(`Point mark "${id}" requires an existing dataset.`);
224
184
  }
225
185
 
226
- const x = resolveMappedValues(this, layer, dataset, "x");
227
- const y = resolveMappedValues(this, layer, dataset, "y");
228
- const mappedFill = resolveMappedValues(this, layer, dataset, "color");
229
- const area = resolveMappedValues(this, layer, dataset, "size");
230
- const encodedShape = resolveMappedValues(this, layer, dataset, "shape");
231
- const encodedOpacity = resolveMappedValues(this, layer, dataset, "opacity");
186
+ const { x, y } = resolvePointPositions(this, layer, dataset);
187
+ const mappedFill = resolveRowEncodingValues(this, layer, dataset, "color");
188
+ const area = resolveRowEncodingValues(this, layer, dataset, "size");
189
+ const encodedShape = resolveRowEncodingValues(this, layer, dataset, "shape");
190
+ const encodedOpacity = resolveRowEncodingValues(this, layer, dataset, "opacity");
232
191
  const config = this.markConfigs[id] ?? {};
233
192
  const fill = mappedFill ?? config.fill ?? DEFAULT_POINT_FILL;
234
193
  const shapes = encodedShape ?? dataset.values.map(() => config.shape ?? "circle");
@@ -0,0 +1 @@
1
+ export { registerPointMarkActions } from "./actions.js";