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
@@ -0,0 +1,50 @@
1
+ import { action } from "../../core/action.js";
2
+ import {
3
+ applyFacadeGuides,
4
+ normalizeAppearance,
5
+ normalizeEncoding,
6
+ normalizeFieldEncoding,
7
+ normalizeGuides,
8
+ normalizeTargetOptions,
9
+ positionArgs,
10
+ resolveFacadeData,
11
+ resolveFacadeId,
12
+ targetArgs,
13
+ validateFacadeOptions
14
+ } from "./shared.js";
15
+
16
+ const OPTIONS = Object.freeze([
17
+ "id", "data", "coordinate", "x", "y", "color", "width", "bar", "guides"
18
+ ]);
19
+ const BAR_OPTIONS = Object.freeze([
20
+ "fill", "opacity", "stroke", "strokeWidth"
21
+ ]);
22
+
23
+ export const createBarPlot = action(
24
+ {
25
+ op: "createBarPlot",
26
+ description: "Create a Cartesian bar plot from existing chart data."
27
+ },
28
+ function (args = {}) {
29
+ validateFacadeOptions(args, OPTIONS, "createBarPlot");
30
+ const id = resolveFacadeId(this, args.id, {
31
+ defaultId: "barPlot",
32
+ operation: "createBarPlot"
33
+ });
34
+ const data = resolveFacadeData(this, args.data, "createBarPlot");
35
+ const bar = normalizeAppearance(args.bar, BAR_OPTIONS, "createBarPlot bar");
36
+ const x = normalizeFieldEncoding(args.x, "createBarPlot x");
37
+ const y = normalizeFieldEncoding(args.y, "createBarPlot y");
38
+ const color = normalizeEncoding(args.color, "createBarPlot color");
39
+ const width = normalizeTargetOptions(args.width, "createBarPlot width");
40
+ const guides = normalizeGuides(args.guides, "createBarPlot");
41
+
42
+ let next = this
43
+ .createBarMark({ id, data, ...bar })
44
+ .encodeX(positionArgs(x, { target: id, coordinate: args.coordinate }))
45
+ .encodeY(positionArgs(y, { target: id, coordinate: args.coordinate }));
46
+ if (color !== undefined) next = next.encodeColor(targetArgs(color, id));
47
+ if (width !== undefined) next = next.encodeBarWidth(targetArgs(width, id));
48
+ return applyFacadeGuides(next, guides);
49
+ }
50
+ );
@@ -0,0 +1,205 @@
1
+ import { action } from "../../core/action.js";
2
+ import { isPlainObject } from "../../core/immutable.js";
3
+ import {
4
+ validateNonEmptyString,
5
+ validateOptionObject
6
+ } from "../../core/validation.js";
7
+ import { requireDataset } from "../../selectors/datasets.js";
8
+ import {
9
+ applyFacadeGuides,
10
+ normalizeAppearance,
11
+ normalizeFieldEncoding,
12
+ normalizeGuides,
13
+ positionArgs,
14
+ resolveFacadeData,
15
+ resolveFacadeId,
16
+ targetArgs,
17
+ validateFacadeOptions
18
+ } from "./shared.js";
19
+
20
+ const OPTIONS = Object.freeze([
21
+ "id", "data", "coordinate", "x", "y", "bin", "color", "rect", "guides"
22
+ ]);
23
+ const RECT_OPTIONS = Object.freeze([
24
+ "opacity", "stroke", "strokeWidth"
25
+ ]);
26
+ const BIN_OPTIONS = Object.freeze([
27
+ "bins", "extent", "includeEmpty"
28
+ ]);
29
+ const BINNED_POSITION_OPTIONS = Object.freeze([
30
+ "field", "fieldType", "scale"
31
+ ]);
32
+ const BINNED_COLOR_OPTIONS = Object.freeze([
33
+ "scale", "palette"
34
+ ]);
35
+
36
+ function normalizeBin(value) {
37
+ if (!isPlainObject(value)) {
38
+ throw new TypeError("createHeatmap bin must be a plain object.");
39
+ }
40
+ validateOptionObject(value, BIN_OPTIONS, "createHeatmap bin");
41
+ return {
42
+ ...value,
43
+ includeEmpty: value.includeEmpty ?? true
44
+ };
45
+ }
46
+
47
+ function normalizeBinnedPosition(value, channel) {
48
+ const label = `createHeatmap ${channel}`;
49
+ const encoding = normalizeFieldEncoding(value, label);
50
+ validateOptionObject(encoding, BINNED_POSITION_OPTIONS, label);
51
+ const field = validateNonEmptyString(encoding.field, `${label} field`);
52
+ const fieldType = encoding.fieldType ?? "quantitative";
53
+ if (fieldType !== "quantitative") {
54
+ throw new Error(`${label} requires a quantitative field.`);
55
+ }
56
+ if (encoding.scale !== undefined && !isPlainObject(encoding.scale)) {
57
+ throw new TypeError(`${label} scale must be a plain object.`);
58
+ }
59
+ return { ...encoding, field, fieldType };
60
+ }
61
+
62
+ function normalizeBinnedColor(value) {
63
+ if (value === undefined) return {};
64
+ if (!isPlainObject(value)) {
65
+ throw new TypeError("createHeatmap binned color must be a plain object.");
66
+ }
67
+ validateOptionObject(value, BINNED_COLOR_OPTIONS, "createHeatmap binned color");
68
+ return { ...value };
69
+ }
70
+
71
+ function resolvedPosition(encoding, field, extent) {
72
+ const scale = encoding.scale ?? {};
73
+ return {
74
+ field,
75
+ fieldType: "quantitative",
76
+ scale: {
77
+ type: "linear",
78
+ nice: false,
79
+ zero: false,
80
+ ...scale,
81
+ domain: scale.domain === undefined || scale.domain === "auto"
82
+ ? extent
83
+ : scale.domain
84
+ }
85
+ };
86
+ }
87
+
88
+ function axisWithTitle(value, text) {
89
+ if (value === false) return false;
90
+ if (value !== undefined && !isPlainObject(value)) {
91
+ throw new TypeError("createHeatmap binned axis must be false or a plain object.");
92
+ }
93
+ if (value?.title !== undefined && !isPlainObject(value.title)) {
94
+ throw new TypeError("createHeatmap binned axis title must be a plain object.");
95
+ }
96
+ return {
97
+ ...value,
98
+ title: {
99
+ text,
100
+ ...value?.title
101
+ }
102
+ };
103
+ }
104
+
105
+ function binnedGuides(guides, xTitle, yTitle) {
106
+ if (guides === false) return false;
107
+ for (const key of ["axes", "legend"]) {
108
+ if (
109
+ guides[key] !== undefined &&
110
+ guides[key] !== false &&
111
+ !isPlainObject(guides[key])
112
+ ) {
113
+ throw new TypeError(
114
+ `createHeatmap guides ${key} must be false or a plain object.`
115
+ );
116
+ }
117
+ }
118
+ const axes = guides.axes === false
119
+ ? false
120
+ : {
121
+ ...guides.axes,
122
+ x: axisWithTitle(guides.axes?.x, xTitle),
123
+ y: axisWithTitle(guides.axes?.y, yTitle)
124
+ };
125
+ const legend = guides.legend === false
126
+ ? false
127
+ : { title: "Count", ...guides.legend };
128
+ return {
129
+ ...guides,
130
+ axes,
131
+ grid: guides.grid ?? false,
132
+ legend
133
+ };
134
+ }
135
+
136
+ function createPreGriddedHeatmap(program, args, { id, data, rect, guides }) {
137
+ const x = normalizeFieldEncoding(args.x, "createHeatmap x");
138
+ const y = normalizeFieldEncoding(args.y, "createHeatmap y");
139
+ const color = normalizeFieldEncoding(args.color, "createHeatmap color");
140
+ const next = program
141
+ .createRectMark({ id, data, ...rect })
142
+ .encodeX(positionArgs(x, { target: id, coordinate: args.coordinate }))
143
+ .encodeY(positionArgs(y, { target: id, coordinate: args.coordinate }))
144
+ .encodeColor(targetArgs(color, id));
145
+ return applyFacadeGuides(next, guides);
146
+ }
147
+
148
+ function createBinnedHeatmap(program, args, { id, data, rect, guides }) {
149
+ const x = normalizeBinnedPosition(args.x, "x");
150
+ const y = normalizeBinnedPosition(args.y, "y");
151
+ const bin = normalizeBin(args.bin);
152
+ const color = normalizeBinnedColor(args.color);
153
+ const resolvedGuides = binnedGuides(guides, x.field, y.field);
154
+ const generatedData = `${id}Bin2DData`;
155
+ const binned = program.createBin2DData({
156
+ id: generatedData,
157
+ source: data,
158
+ x: x.field,
159
+ y: y.field,
160
+ ...bin
161
+ });
162
+ const dataset = requireDataset(binned, generatedData);
163
+ const transform = dataset.transform[0];
164
+ const resolved = transform.resolved;
165
+ const xEncoding = resolvedPosition(x, transform.as.x0, resolved.extent.x);
166
+ const yEncoding = resolvedPosition(y, transform.as.y0, resolved.extent.y);
167
+
168
+ const next = binned
169
+ .createRectMark({ id, data: generatedData, ...rect })
170
+ .encodeX(positionArgs(xEncoding, { target: id, coordinate: args.coordinate }))
171
+ .encodeX2({ target: id, field: transform.as.x1, fieldType: "quantitative" })
172
+ .encodeY(positionArgs(yEncoding, { target: id, coordinate: args.coordinate }))
173
+ .encodeY2({ target: id, field: transform.as.y1, fieldType: "quantitative" })
174
+ .encodeColor({
175
+ target: id,
176
+ field: transform.as.count,
177
+ fieldType: "quantitative",
178
+ ...color
179
+ });
180
+ return applyFacadeGuides(
181
+ next,
182
+ resolvedGuides
183
+ );
184
+ }
185
+
186
+ export const createHeatmap = action(
187
+ {
188
+ op: "createHeatmap",
189
+ description: "Create a pre-gridded or rectangularly binned heatmap."
190
+ },
191
+ function (args = {}) {
192
+ validateFacadeOptions(args, OPTIONS, "createHeatmap");
193
+ const id = resolveFacadeId(this, args.id, {
194
+ defaultId: "heatmap",
195
+ operation: "createHeatmap"
196
+ });
197
+ const data = resolveFacadeData(this, args.data, "createHeatmap");
198
+ const rect = normalizeAppearance(args.rect, RECT_OPTIONS, "createHeatmap rect");
199
+ const guides = normalizeGuides(args.guides, "createHeatmap");
200
+ const shared = { id, data, rect, guides };
201
+ return args.bin === undefined
202
+ ? createPreGriddedHeatmap(this, args, shared)
203
+ : createBinnedHeatmap(this, args, shared);
204
+ }
205
+ );
@@ -0,0 +1,58 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateNonEmptyString } from "../../core/validation.js";
3
+ import {
4
+ applyFacadeGuides,
5
+ normalizeAppearance,
6
+ normalizeEncoding,
7
+ normalizeGuides,
8
+ resolveFacadeData,
9
+ resolveFacadeId,
10
+ targetArgs,
11
+ validateFacadeOptions
12
+ } from "./shared.js";
13
+
14
+ const OPTIONS = Object.freeze([
15
+ "id", "data", "coordinate", "field", "maxBins", "binStep",
16
+ "binBoundaries", "stack", "xScale", "yScale", "color", "bar", "guides"
17
+ ]);
18
+ const BAR_OPTIONS = Object.freeze([
19
+ "fill", "opacity", "stroke", "strokeWidth"
20
+ ]);
21
+ const HISTOGRAM_OPTIONS = Object.freeze([
22
+ "maxBins", "binStep", "binBoundaries", "stack", "xScale", "yScale"
23
+ ]);
24
+
25
+ export const createHistogram = action(
26
+ {
27
+ op: "createHistogram",
28
+ description: "Create a binned count histogram from existing chart data."
29
+ },
30
+ function (args = {}) {
31
+ validateFacadeOptions(args, OPTIONS, "createHistogram");
32
+ const id = resolveFacadeId(this, args.id, {
33
+ defaultId: "histogram",
34
+ operation: "createHistogram"
35
+ });
36
+ const data = resolveFacadeData(this, args.data, "createHistogram");
37
+ const field = validateNonEmptyString(args.field, "createHistogram field");
38
+ const bar = normalizeAppearance(args.bar, BAR_OPTIONS, "createHistogram bar");
39
+ const color = normalizeEncoding(args.color, "createHistogram color");
40
+ const guides = normalizeGuides(args.guides, "createHistogram");
41
+ const histogram = Object.fromEntries(
42
+ HISTOGRAM_OPTIONS
43
+ .filter(key => Object.hasOwn(args, key))
44
+ .map(key => [key, args[key]])
45
+ );
46
+
47
+ let next = this
48
+ .createBarMark({ id, data, ...bar })
49
+ .encodeHistogram({
50
+ field,
51
+ target: id,
52
+ ...(args.coordinate === undefined ? {} : { coordinate: args.coordinate }),
53
+ ...histogram
54
+ });
55
+ if (color !== undefined) next = next.encodeColor(targetArgs(color, id));
56
+ return applyFacadeGuides(next, guides);
57
+ }
58
+ );
@@ -0,0 +1,15 @@
1
+ import { createBarPlot } from "./bar.js";
2
+ import { createHeatmap } from "./heatmap.js";
3
+ import { createHistogram } from "./histogram.js";
4
+ import { createLinePlot } from "./line.js";
5
+ import { createScatterPlot } from "./scatter.js";
6
+ import { createParallelCoordinates } from "./parallel.js";
7
+
8
+ export function registerChartActions(ProgramClass) {
9
+ ProgramClass.prototype.createBarPlot = createBarPlot;
10
+ ProgramClass.prototype.createHeatmap = createHeatmap;
11
+ ProgramClass.prototype.createHistogram = createHistogram;
12
+ ProgramClass.prototype.createScatterPlot = createScatterPlot;
13
+ ProgramClass.prototype.createLinePlot = createLinePlot;
14
+ ProgramClass.prototype.createParallelCoordinates = createParallelCoordinates;
15
+ }
@@ -0,0 +1,69 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateNonEmptyString } from "../../core/validation.js";
3
+ import {
4
+ applyFacadeGuides,
5
+ normalizeAppearance,
6
+ normalizeEncoding,
7
+ normalizeFieldEncoding,
8
+ normalizeGuides,
9
+ normalizeStrokeDashEncoding,
10
+ positionArgs,
11
+ resolveFacadeData,
12
+ resolveFacadeId,
13
+ targetArgs,
14
+ validateFacadeOptions
15
+ } from "./shared.js";
16
+
17
+ const OPTIONS = Object.freeze([
18
+ "id", "data", "coordinate", "x", "y", "color", "groupBy",
19
+ "strokeDash", "line", "guides"
20
+ ]);
21
+ const LINE_OPTIONS = Object.freeze([
22
+ "strokeWidth", "curve", "stroke", "opacity", "closed"
23
+ ]);
24
+
25
+ export const createLinePlot = action(
26
+ {
27
+ op: "createLinePlot",
28
+ description: "Create a Cartesian line plot from existing chart data."
29
+ },
30
+ function (args = {}) {
31
+ validateFacadeOptions(args, OPTIONS, "createLinePlot");
32
+ const id = resolveFacadeId(this, args.id, {
33
+ defaultId: "linePlot",
34
+ operation: "createLinePlot"
35
+ });
36
+ const data = resolveFacadeData(this, args.data, "createLinePlot");
37
+ const line = normalizeAppearance(
38
+ args.line,
39
+ LINE_OPTIONS,
40
+ "createLinePlot line"
41
+ );
42
+ if (line.closed === true) {
43
+ throw new Error(
44
+ "createLinePlot is Cartesian; closed lines require Polar line authoring."
45
+ );
46
+ }
47
+ const x = normalizeFieldEncoding(args.x, "createLinePlot x");
48
+ const y = normalizeFieldEncoding(args.y, "createLinePlot y");
49
+ const color = normalizeEncoding(args.color, "createLinePlot color");
50
+ const strokeDash = normalizeStrokeDashEncoding(args.strokeDash);
51
+ const groupBy = args.groupBy === undefined
52
+ ? undefined
53
+ : validateNonEmptyString(args.groupBy, "createLinePlot groupBy");
54
+ const guides = normalizeGuides(args.guides, "createLinePlot");
55
+
56
+ let next = this
57
+ .createLineMark({ id, data, ...line })
58
+ .encodeX(positionArgs(x, { target: id, coordinate: args.coordinate }))
59
+ .encodeY(positionArgs(y, { target: id, coordinate: args.coordinate }));
60
+ if (color !== undefined) next = next.encodeColor(targetArgs(color, id));
61
+ if (groupBy !== undefined) {
62
+ next = next.encodeGroup({ field: groupBy, target: id });
63
+ }
64
+ if (strokeDash !== undefined) {
65
+ next = next.encodeStrokeDash(targetArgs(strokeDash, id));
66
+ }
67
+ return applyFacadeGuides(next, guides);
68
+ }
69
+ );
@@ -0,0 +1,76 @@
1
+ import { action } from "../../core/action.js";
2
+ import { resolveParallelCoordinate } from "../coordinates/parallel.js";
3
+ import {
4
+ applyFacadeGuides,
5
+ normalizeAppearance,
6
+ normalizeEncoding,
7
+ normalizeGuides,
8
+ normalizeStrokeDashEncoding,
9
+ resolveFacadeData,
10
+ resolveFacadeId,
11
+ targetArgs,
12
+ validateFacadeOptions
13
+ } from "./shared.js";
14
+
15
+ const OPTIONS = Object.freeze([
16
+ "id", "data", "coordinate", "dimensions", "key", "missing",
17
+ "color", "strokeDash", "line", "guides"
18
+ ]);
19
+ const LINE_OPTIONS = Object.freeze([
20
+ "strokeWidth", "stroke", "opacity", "curve", "closed"
21
+ ]);
22
+
23
+ export const createParallelCoordinates = action(
24
+ {
25
+ op: "createParallelCoordinates",
26
+ description: "Create a complete Parallel-coordinates chart."
27
+ },
28
+ function (args = {}) {
29
+ validateFacadeOptions(args, OPTIONS, "createParallelCoordinates");
30
+ const id = resolveFacadeId(this, args.id, {
31
+ defaultId: "parallelCoordinates",
32
+ operation: "createParallelCoordinates"
33
+ });
34
+ const data = resolveFacadeData(this, args.data, "createParallelCoordinates");
35
+ const coordinate = resolveParallelCoordinate(this, {
36
+ requested: args.coordinate,
37
+ operation: "createParallelCoordinates",
38
+ useCurrent: true
39
+ }).id;
40
+ const line = normalizeAppearance(
41
+ args.line,
42
+ LINE_OPTIONS,
43
+ "createParallelCoordinates line"
44
+ );
45
+ if (line.closed === true || (line.curve !== undefined && line.curve !== "linear")) {
46
+ throw new Error(
47
+ "createParallelCoordinates requires a linear open line."
48
+ );
49
+ }
50
+ const color = normalizeEncoding(
51
+ args.color,
52
+ "createParallelCoordinates color"
53
+ );
54
+ const strokeDash = normalizeStrokeDashEncoding(
55
+ args.strokeDash,
56
+ "createParallelCoordinates strokeDash"
57
+ );
58
+ const guides = normalizeGuides(args.guides, "createParallelCoordinates");
59
+
60
+ let next = this
61
+ .createCoordinate({ id: coordinate, type: "parallel" })
62
+ .createLineMark({ id, data, ...line })
63
+ .encodeParallelCoordinates({
64
+ target: id,
65
+ coordinate,
66
+ dimensions: args.dimensions,
67
+ ...(args.key === undefined ? {} : { key: args.key }),
68
+ ...(args.missing === undefined ? {} : { missing: args.missing })
69
+ });
70
+ if (color !== undefined) next = next.encodeColor(targetArgs(color, id));
71
+ if (strokeDash !== undefined) {
72
+ next = next.encodeStrokeDash(targetArgs(strokeDash, id));
73
+ }
74
+ return applyFacadeGuides(next, guides);
75
+ }
76
+ );
@@ -0,0 +1,56 @@
1
+ import { action } from "../../core/action.js";
2
+ import {
3
+ applyFacadeGuides,
4
+ normalizeAppearance,
5
+ normalizeEncoding,
6
+ normalizeFieldEncoding,
7
+ normalizeGuides,
8
+ positionArgs,
9
+ resolveFacadeData,
10
+ resolveFacadeId,
11
+ targetArgs,
12
+ validateFacadeOptions
13
+ } from "./shared.js";
14
+
15
+ const OPTIONS = Object.freeze([
16
+ "id", "data", "coordinate", "x", "y", "color", "size", "shape",
17
+ "point", "guides"
18
+ ]);
19
+ const POINT_OPTIONS = Object.freeze([
20
+ "shape", "fill", "opacity", "stroke", "strokeWidth"
21
+ ]);
22
+
23
+ export const createScatterPlot = action(
24
+ {
25
+ op: "createScatterPlot",
26
+ description: "Create a Cartesian scatter plot from existing chart data."
27
+ },
28
+ function (args = {}) {
29
+ validateFacadeOptions(args, OPTIONS, "createScatterPlot");
30
+ const id = resolveFacadeId(this, args.id, {
31
+ defaultId: "scatterPlot",
32
+ operation: "createScatterPlot"
33
+ });
34
+ const data = resolveFacadeData(this, args.data, "createScatterPlot");
35
+ const point = normalizeAppearance(
36
+ args.point,
37
+ POINT_OPTIONS,
38
+ "createScatterPlot point"
39
+ );
40
+ const x = normalizeFieldEncoding(args.x, "createScatterPlot x");
41
+ const y = normalizeFieldEncoding(args.y, "createScatterPlot y");
42
+ const color = normalizeEncoding(args.color, "createScatterPlot color");
43
+ const size = normalizeEncoding(args.size, "createScatterPlot size");
44
+ const shape = normalizeEncoding(args.shape, "createScatterPlot shape");
45
+ const guides = normalizeGuides(args.guides, "createScatterPlot");
46
+
47
+ let next = this
48
+ .createPointMark({ id, data, ...point })
49
+ .encodeX(positionArgs(x, { target: id, coordinate: args.coordinate }))
50
+ .encodeY(positionArgs(y, { target: id, coordinate: args.coordinate }));
51
+ if (color !== undefined) next = next.encodeColor(targetArgs(color, id));
52
+ if (size !== undefined) next = next.encodeSize(targetArgs(size, id));
53
+ if (shape !== undefined) next = next.encodeShape(targetArgs(shape, id));
54
+ return applyFacadeGuides(next, guides);
55
+ }
56
+ );
@@ -0,0 +1,121 @@
1
+ import { resolveOptionalUserId, validateUserId } from "../../core/identifiers.js";
2
+ import { isPlainObject } from "../../core/immutable.js";
3
+ import { validateOptionObject } from "../../core/validation.js";
4
+ import { findDataset } from "../../selectors/datasets.js";
5
+ import { hasLayer } from "../../selectors/layers.js";
6
+
7
+ export function validateFacadeOptions(args, supported, operation) {
8
+ return validateOptionObject(args, supported, operation);
9
+ }
10
+
11
+ export function resolveFacadeData(program, requested, operation) {
12
+ if (requested !== undefined) {
13
+ const id = validateUserId(requested, `${operation} dataset id`);
14
+ if (findDataset(program, id) === undefined) {
15
+ throw new Error(`Unknown dataset "${id}".`);
16
+ }
17
+ return id;
18
+ }
19
+
20
+ const current = program.context.currentData;
21
+ if (current !== undefined && findDataset(program, current) !== undefined) {
22
+ return current;
23
+ }
24
+ const datasets = program.semanticSpec.datasets;
25
+ if (datasets.length === 1) return datasets[0].id;
26
+ if (datasets.length > 1) {
27
+ throw new Error(
28
+ `${operation} requires data when multiple datasets are available.`
29
+ );
30
+ }
31
+ throw new Error(`${operation} requires data or one inferable dataset.`);
32
+ }
33
+
34
+ export function resolveFacadeId(program, requested, {
35
+ defaultId,
36
+ operation
37
+ }) {
38
+ return resolveOptionalUserId(requested, {
39
+ defaultId,
40
+ label: `${operation} id`,
41
+ operation,
42
+ ambiguous: hasLayer(program, defaultId) ||
43
+ program.graphicSpec.objects[defaultId] !== undefined
44
+ });
45
+ }
46
+
47
+ export function normalizeFieldEncoding(value, label) {
48
+ if (typeof value === "string") return { field: value };
49
+ if (!isPlainObject(value)) {
50
+ throw new TypeError(`${label} must be a field string or a plain object.`);
51
+ }
52
+ if (Object.hasOwn(value, "target") || Object.hasOwn(value, "coordinate")) {
53
+ throw new Error(
54
+ `${label} target and coordinate are owned by the chart facade.`
55
+ );
56
+ }
57
+ return { ...value };
58
+ }
59
+
60
+ export function normalizeEncoding(value, label) {
61
+ if (value === undefined) return undefined;
62
+ return normalizeFieldEncoding(value, label);
63
+ }
64
+
65
+ export function normalizeStrokeDashEncoding(
66
+ value,
67
+ label = "createLinePlot strokeDash"
68
+ ) {
69
+ if (value === undefined) return undefined;
70
+ if (!isPlainObject(value)) {
71
+ throw new TypeError(
72
+ `${label} must be an encodeStrokeDash option object.`
73
+ );
74
+ }
75
+ if (Object.hasOwn(value, "target")) {
76
+ throw new Error(`${label} target is owned by the chart facade.`);
77
+ }
78
+ return { ...value };
79
+ }
80
+
81
+ export function normalizeAppearance(value, supported, label) {
82
+ if (value === undefined) return {};
83
+ validateOptionObject(value, supported, label);
84
+ return { ...value };
85
+ }
86
+
87
+ export function normalizeTargetOptions(value, label) {
88
+ if (value === undefined) return undefined;
89
+ if (!isPlainObject(value)) {
90
+ throw new TypeError(`${label} must be a plain object.`);
91
+ }
92
+ if (Object.hasOwn(value, "target")) {
93
+ throw new Error(`${label} target is owned by the chart facade.`);
94
+ }
95
+ return { ...value };
96
+ }
97
+
98
+ export function normalizeGuides(value, operation) {
99
+ if (value === false) return false;
100
+ if (value === undefined) return {};
101
+ if (!isPlainObject(value)) {
102
+ throw new TypeError(`${operation} guides must be false or a plain object.`);
103
+ }
104
+ return { ...value };
105
+ }
106
+
107
+ export function positionArgs(encoding, { target, coordinate }) {
108
+ return {
109
+ ...encoding,
110
+ target,
111
+ ...(coordinate === undefined ? {} : { coordinate })
112
+ };
113
+ }
114
+
115
+ export function targetArgs(encoding, target) {
116
+ return { ...encoding, target };
117
+ }
118
+
119
+ export function applyFacadeGuides(program, guides) {
120
+ return guides === false ? program : program.createGuides(guides);
121
+ }