ggaction 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +108 -0
  4. package/package.json +84 -0
  5. package/src/AGENTS.md +119 -0
  6. package/src/ChartProgram.js +10 -0
  7. package/src/actions/boxPlots/components.js +88 -0
  8. package/src/actions/boxPlots/create.js +84 -0
  9. package/src/actions/boxPlots/index.js +10 -0
  10. package/src/actions/boxPlots/materialize.js +183 -0
  11. package/src/actions/boxPlots/options.js +147 -0
  12. package/src/actions/boxPlots/resolve.js +38 -0
  13. package/src/actions/canvas/actions.js +144 -0
  14. package/src/actions/canvas/index.js +6 -0
  15. package/src/actions/coordinates/actions.js +85 -0
  16. package/src/actions/coordinates/index.js +5 -0
  17. package/src/actions/data/box.js +40 -0
  18. package/src/actions/data/create.js +33 -0
  19. package/src/actions/data/density.js +62 -0
  20. package/src/actions/data/derived.js +52 -0
  21. package/src/actions/data/filter.js +168 -0
  22. package/src/actions/data/index.js +32 -0
  23. package/src/actions/data/interval.js +69 -0
  24. package/src/actions/data/regression.js +71 -0
  25. package/src/actions/data/shared.js +25 -0
  26. package/src/actions/encodings/appearance.js +243 -0
  27. package/src/actions/encodings/barWidth.js +60 -0
  28. package/src/actions/encodings/color.js +329 -0
  29. package/src/actions/encodings/density.js +225 -0
  30. package/src/actions/encodings/histogram.js +50 -0
  31. package/src/actions/encodings/index.js +23 -0
  32. package/src/actions/encodings/offset.js +104 -0
  33. package/src/actions/encodings/position/policies/area.js +24 -0
  34. package/src/actions/encodings/position/policies/bar.js +110 -0
  35. package/src/actions/encodings/position/policies/common.js +17 -0
  36. package/src/actions/encodings/position/policies/index.js +25 -0
  37. package/src/actions/encodings/position/policies/line.js +74 -0
  38. package/src/actions/encodings/position/policies/point.js +19 -0
  39. package/src/actions/encodings/position/policies/rule.js +17 -0
  40. package/src/actions/encodings/position/resolve.js +170 -0
  41. package/src/actions/encodings/position.js +176 -0
  42. package/src/actions/encodings/ranged.js +276 -0
  43. package/src/actions/encodings/ruleAppearance.js +47 -0
  44. package/src/actions/encodings/shared.js +164 -0
  45. package/src/actions/encodings/strokeDash.js +145 -0
  46. package/src/actions/errorBands/components.js +81 -0
  47. package/src/actions/errorBands/create.js +159 -0
  48. package/src/actions/errorBands/index.js +7 -0
  49. package/src/actions/errorBands/resolve.js +28 -0
  50. package/src/actions/errorBars/components.js +62 -0
  51. package/src/actions/errorBars/create.js +144 -0
  52. package/src/actions/errorBars/index.js +7 -0
  53. package/src/actions/errorBars/resolve.js +25 -0
  54. package/src/actions/guides/axes/axes.js +204 -0
  55. package/src/actions/guides/axes/axis.js +131 -0
  56. package/src/actions/guides/axes/index.js +17 -0
  57. package/src/actions/guides/axes/labels.js +231 -0
  58. package/src/actions/guides/axes/lines.js +149 -0
  59. package/src/actions/guides/axes/policy.js +155 -0
  60. package/src/actions/guides/axes/tickGroups.js +149 -0
  61. package/src/actions/guides/axes/ticks.js +179 -0
  62. package/src/actions/guides/axes/titles.js +264 -0
  63. package/src/actions/guides/grids/grid.js +228 -0
  64. package/src/actions/guides/grids/index.js +1 -0
  65. package/src/actions/guides/grids/resolve.js +258 -0
  66. package/src/actions/guides/guides.js +138 -0
  67. package/src/actions/guides/index.js +11 -0
  68. package/src/actions/guides/legends/categorical/actions.js +329 -0
  69. package/src/actions/guides/legends/categorical/components.js +140 -0
  70. package/src/actions/guides/legends/categorical/index.js +49 -0
  71. package/src/actions/guides/legends/categorical/layout.js +445 -0
  72. package/src/actions/guides/legends/categorical/options.js +188 -0
  73. package/src/actions/guides/legends/categorical/recipes.js +120 -0
  74. package/src/actions/guides/legends/categorical/resolve.js +131 -0
  75. package/src/actions/guides/legends/categorical/symbols.js +280 -0
  76. package/src/actions/guides/legends/categorical/validation.js +27 -0
  77. package/src/actions/guides/legends/continuous/common.js +273 -0
  78. package/src/actions/guides/legends/continuous/gradient.js +363 -0
  79. package/src/actions/guides/legends/continuous/index.js +25 -0
  80. package/src/actions/guides/legends/continuous/interval.js +234 -0
  81. package/src/actions/guides/legends/continuous/opacity.js +382 -0
  82. package/src/actions/guides/legends/edit.js +314 -0
  83. package/src/actions/guides/legends/index.js +9 -0
  84. package/src/actions/guides/legends/size.js +220 -0
  85. package/src/actions/guides/tickValues.js +84 -0
  86. package/src/actions/index.js +31 -0
  87. package/src/actions/intervals/resolve.js +352 -0
  88. package/src/actions/marks/area.js +369 -0
  89. package/src/actions/marks/bar/create.js +46 -0
  90. package/src/actions/marks/bar/edit.js +90 -0
  91. package/src/actions/marks/bar/index.js +9 -0
  92. package/src/actions/marks/bar/materialize.js +130 -0
  93. package/src/actions/marks/index.js +13 -0
  94. package/src/actions/marks/line.js +248 -0
  95. package/src/actions/marks/point.js +420 -0
  96. package/src/actions/marks/rule.js +287 -0
  97. package/src/actions/marks/shared.js +112 -0
  98. package/src/actions/primitives/createGraphics.js +163 -0
  99. package/src/actions/primitives/editGraphics.js +208 -0
  100. package/src/actions/primitives/index.js +9 -0
  101. package/src/actions/primitives/semantic.js +168 -0
  102. package/src/actions/primitives/semanticValue.js +182 -0
  103. package/src/actions/regression/components.js +216 -0
  104. package/src/actions/regression/create.js +102 -0
  105. package/src/actions/regression/index.js +23 -0
  106. package/src/actions/regression/resolve.js +57 -0
  107. package/src/actions/scales/consumers.js +283 -0
  108. package/src/actions/scales/create.js +148 -0
  109. package/src/actions/scales/definitions.js +319 -0
  110. package/src/actions/scales/edit.js +290 -0
  111. package/src/actions/scales/index.js +11 -0
  112. package/src/actions/scales/materialize.js +466 -0
  113. package/src/actions/scales/quantitativeColor.js +45 -0
  114. package/src/actions/selection/actions.js +384 -0
  115. package/src/actions/selection/index.js +23 -0
  116. package/src/actions/titles/actions.js +235 -0
  117. package/src/actions/titles/index.js +19 -0
  118. package/src/actions/titles/resolve.js +321 -0
  119. package/src/core/ChartProgram.js +316 -0
  120. package/src/core/action.js +120 -0
  121. package/src/core/identifiers.js +28 -0
  122. package/src/core/immutable.js +94 -0
  123. package/src/core/specs.js +29 -0
  124. package/src/core/textMetrics.js +23 -0
  125. package/src/core/validation.js +54 -0
  126. package/src/core/vocabulary.js +58 -0
  127. package/src/extension.js +2 -0
  128. package/src/grammar/aggregate.js +259 -0
  129. package/src/grammar/areaSeries.js +217 -0
  130. package/src/grammar/bars/aggregate.js +98 -0
  131. package/src/grammar/bars/geometry.js +54 -0
  132. package/src/grammar/bars/policy.js +90 -0
  133. package/src/grammar/boxPlot.js +140 -0
  134. package/src/grammar/coordinates.js +25 -0
  135. package/src/grammar/curveCommands.js +356 -0
  136. package/src/grammar/density.js +296 -0
  137. package/src/grammar/filter.js +169 -0
  138. package/src/grammar/histogram.js +332 -0
  139. package/src/grammar/interval.js +343 -0
  140. package/src/grammar/lineSeries.js +197 -0
  141. package/src/grammar/markFilter.js +46 -0
  142. package/src/grammar/markSelection.js +326 -0
  143. package/src/grammar/palettes.js +208 -0
  144. package/src/grammar/pathCommands.js +86 -0
  145. package/src/grammar/pointShapes.js +164 -0
  146. package/src/grammar/positionCompatibility.js +32 -0
  147. package/src/grammar/regression.js +578 -0
  148. package/src/grammar/ruleAppearance.js +12 -0
  149. package/src/grammar/rules.js +90 -0
  150. package/src/grammar/scales/appearance.js +185 -0
  151. package/src/grammar/scales/color.js +349 -0
  152. package/src/grammar/scales/continuous.js +111 -0
  153. package/src/grammar/scales/definition.js +153 -0
  154. package/src/grammar/scales/discretized.js +159 -0
  155. package/src/grammar/scales/fields.js +133 -0
  156. package/src/grammar/scales/index.js +12 -0
  157. package/src/grammar/scales/mapping.js +27 -0
  158. package/src/grammar/scales/ordinal.js +221 -0
  159. package/src/grammar/scales/policies.js +50 -0
  160. package/src/grammar/scales/temporal.js +53 -0
  161. package/src/grammar/scales/transformed.js +236 -0
  162. package/src/grammar/scales/types.js +102 -0
  163. package/src/grammar/scales/validation.js +66 -0
  164. package/src/grammar/scales.js +1 -0
  165. package/src/grammar/schemas/concreteGraphic.js +73 -0
  166. package/src/grammar/schemas/graphic.js +131 -0
  167. package/src/grammar/schemas/graphicBounds.js +146 -0
  168. package/src/grammar/schemas/graphicTree.js +133 -0
  169. package/src/grammar/schemas/mark.js +4 -0
  170. package/src/grammar/schemas/semanticPath.js +174 -0
  171. package/src/grammar/seriesLayout.js +76 -0
  172. package/src/grammar/ticks.js +149 -0
  173. package/src/grammar/transforms.js +37 -0
  174. package/src/index.js +2 -0
  175. package/src/layout/canvas.js +114 -0
  176. package/src/layout/grid.js +17 -0
  177. package/src/layout/legend.js +51 -0
  178. package/src/layout/text.js +62 -0
  179. package/src/layout/title.js +107 -0
  180. package/src/materialization/bars/aggregate.js +184 -0
  181. package/src/materialization/bars/grouped.js +117 -0
  182. package/src/materialization/bars/histogram.js +163 -0
  183. package/src/materialization/bars/ranged.js +53 -0
  184. package/src/materialization/bars/resolve.js +56 -0
  185. package/src/materialization/dataProvenance.js +15 -0
  186. package/src/materialization/density.js +29 -0
  187. package/src/materialization/dependencies.js +134 -0
  188. package/src/materialization/encodings.js +50 -0
  189. package/src/materialization/graphicHierarchy.js +104 -0
  190. package/src/materialization/legends.js +20 -0
  191. package/src/materialization/marks.js +110 -0
  192. package/src/materialization/planner.js +38 -0
  193. package/src/materialization/selection/filter.js +40 -0
  194. package/src/materialization/selection/items.js +503 -0
  195. package/src/materialization/selection/path.js +37 -0
  196. package/src/materialization/selection/point.js +118 -0
  197. package/src/materialization/selection/policies/area.js +10 -0
  198. package/src/materialization/selection/policies/bar.js +10 -0
  199. package/src/materialization/selection/policies/index.js +40 -0
  200. package/src/materialization/selection/policies/line.js +10 -0
  201. package/src/materialization/selection/policies/point.js +10 -0
  202. package/src/materialization/selection/policies/rule.js +10 -0
  203. package/src/materialization/selection/state.js +67 -0
  204. package/src/materialization/selection/styles.js +183 -0
  205. package/src/renderers/canvas/circle.js +62 -0
  206. package/src/renderers/canvas/index.js +132 -0
  207. package/src/renderers/canvas/line.js +54 -0
  208. package/src/renderers/canvas/path.js +98 -0
  209. package/src/renderers/canvas/rect.js +57 -0
  210. package/src/renderers/canvas/text.js +96 -0
  211. package/src/renderers/canvas/validation.js +23 -0
  212. package/src/renderers/png.js +29 -0
  213. package/src/selectors/coordinates.js +13 -0
  214. package/src/selectors/datasets.js +13 -0
  215. package/src/selectors/index.js +4 -0
  216. package/src/selectors/layers.js +32 -0
  217. package/src/selectors/scales.js +21 -0
  218. package/src/theme/defaults.js +13 -0
  219. package/types/extension.d.ts +14 -0
  220. package/types/index.d.ts +81 -0
  221. package/types/png.d.ts +14 -0
  222. package/types/program.d.ts +1096 -0
@@ -0,0 +1,258 @@
1
+ import { validateUserId } from "../../../core/identifiers.js";
2
+ import { isPlainObject } from "../../../core/immutable.js";
3
+ import {
4
+ validateKeys,
5
+ validateNonEmptyString,
6
+ validateNonNegativeFinite
7
+ } from "../../../core/validation.js";
8
+ import {
9
+ isTransformedScaleType,
10
+ mapContinuousScaleValues
11
+ } from "../../../grammar/scales.js";
12
+ import { resolveGraphicBounds } from "../../../layout/canvas.js";
13
+ import { resolveGridLineGeometry } from "../../../layout/grid.js";
14
+ import { DEFAULT_COLORS } from "../../../theme/defaults.js";
15
+ import { findCoordinate } from "../../../selectors/coordinates.js";
16
+ import {
17
+ inferGridTickConfig,
18
+ valuesFromTickConfig
19
+ } from "../tickValues.js";
20
+ import {
21
+ findGraphicParent,
22
+ walkGraphicDrawOrder
23
+ } from "../../../grammar/schemas/graphicTree.js";
24
+
25
+ const GRID_OPTIONS = Object.freeze([
26
+ "scale", "coordinate", "count", "values", "color", "lineWidth", "strokeDash"
27
+ ]);
28
+ const GRID_EDIT_OPTIONS = Object.freeze([
29
+ "count", "values", "color", "lineWidth", "strokeDash"
30
+ ]);
31
+ const DEFAULT_STYLE = Object.freeze({
32
+ color: DEFAULT_COLORS.grid,
33
+ lineWidth: 1,
34
+ strokeDash: Object.freeze([])
35
+ });
36
+
37
+ export function gridNames(direction) {
38
+ const prefix = direction === "horizontal" ? "Horizontal" : "Vertical";
39
+ return {
40
+ channel: direction === "horizontal" ? "y" : "x",
41
+ create: `create${prefix}Grid`,
42
+ rematerialize: `rematerialize${prefix}Grid`,
43
+ graphic: `${direction}GridLines`
44
+ };
45
+ }
46
+
47
+ function validateStrokeDash(value) {
48
+ if (
49
+ !Array.isArray(value) ||
50
+ value.length % 2 !== 0 ||
51
+ !value.every(item => Number.isFinite(item) && item >= 0)
52
+ ) {
53
+ throw new TypeError(
54
+ "Grid strokeDash must be an even-length array of non-negative finite numbers."
55
+ );
56
+ }
57
+ return value;
58
+ }
59
+
60
+ export function validateGridCreateArgs(args, operation) {
61
+ if (!isPlainObject(args)) {
62
+ throw new TypeError(`${operation} options must be a plain object.`);
63
+ }
64
+ validateKeys(args, GRID_OPTIONS, operation);
65
+ if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
66
+ throw new Error(`${operation} cannot use count and values together.`);
67
+ }
68
+ if (Object.hasOwn(args, "count") && (!Number.isInteger(args.count) || args.count <= 0)) {
69
+ throw new RangeError("Grid count must be a positive integer.");
70
+ }
71
+ if (Object.hasOwn(args, "values") && (
72
+ !Array.isArray(args.values) ||
73
+ args.values.length === 0 ||
74
+ !args.values.every(Number.isFinite)
75
+ )) {
76
+ throw new TypeError("Grid values must be a non-empty finite number array.");
77
+ }
78
+ if (Object.hasOwn(args, "color")) {
79
+ validateNonEmptyString(args.color, "Grid color");
80
+ }
81
+ if (Object.hasOwn(args, "lineWidth")) {
82
+ validateNonNegativeFinite(args.lineWidth, "Grid lineWidth");
83
+ }
84
+ if (Object.hasOwn(args, "strokeDash")) validateStrokeDash(args.strokeDash);
85
+ }
86
+
87
+ export function validateGridEditArgs(args, operation) {
88
+ if (!isPlainObject(args)) {
89
+ throw new TypeError(`${operation} options must be a plain object.`);
90
+ }
91
+ validateKeys(args, GRID_EDIT_OPTIONS, operation);
92
+ if (Object.keys(args).length === 0) {
93
+ throw new TypeError(`${operation} requires at least one option.`);
94
+ }
95
+ if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
96
+ throw new Error(`${operation} cannot use count and values together.`);
97
+ }
98
+ if (Object.hasOwn(args, "count") && (
99
+ !Number.isInteger(args.count) || args.count <= 0
100
+ )) {
101
+ throw new RangeError("Grid count must be a positive integer.");
102
+ }
103
+ if (Object.hasOwn(args, "values") && args.values !== "auto" && (
104
+ !Array.isArray(args.values) ||
105
+ args.values.length === 0 ||
106
+ !args.values.every(Number.isFinite)
107
+ )) {
108
+ throw new TypeError(
109
+ 'Grid values must be "auto" or a non-empty finite number array.'
110
+ );
111
+ }
112
+ if (Object.hasOwn(args, "color")) {
113
+ validateNonEmptyString(args.color, "Grid color");
114
+ }
115
+ if (Object.hasOwn(args, "lineWidth")) {
116
+ validateNonNegativeFinite(args.lineWidth, "Grid lineWidth");
117
+ }
118
+ if (Object.hasOwn(args, "strokeDash")) validateStrokeDash(args.strokeDash);
119
+ }
120
+
121
+ export function resolveGridResources(program, direction, args) {
122
+ const { channel } = gridNames(direction);
123
+ let layers = program.semanticSpec.layers.filter(
124
+ layer => layer.encoding?.[channel]?.scale !== undefined
125
+ );
126
+ if (layers.length === 0) throw new Error(`${direction} grid requires a ${channel} encoding.`);
127
+ const requestedCoordinate = args.coordinate === undefined
128
+ ? undefined
129
+ : validateUserId(args.coordinate, "Grid coordinate id");
130
+ const requestedScale = args.scale === undefined
131
+ ? undefined
132
+ : validateUserId(args.scale, "Grid scale id");
133
+ if (requestedCoordinate !== undefined) {
134
+ layers = layers.filter(layer => layer.coordinate === requestedCoordinate);
135
+ }
136
+ if (requestedScale !== undefined) {
137
+ layers = layers.filter(layer => layer.encoding[channel].scale === requestedScale);
138
+ }
139
+ if (layers.length === 0) throw new Error(`${direction} grid found no matching encoded layers.`);
140
+ const coordinateIds = [...new Set(layers.map(layer => layer.coordinate).filter(Boolean))];
141
+ if (layers.some(layer => layer.coordinate === undefined)) {
142
+ throw new Error(`${direction} grid requires a stored coordinate.`);
143
+ }
144
+ if (coordinateIds.length === 0) throw new Error(`Unknown grid coordinate "${requestedCoordinate}".`);
145
+ if (coordinateIds.length > 1) {
146
+ throw new Error(`${direction} grid found multiple coordinates; provide coordinate explicitly.`);
147
+ }
148
+ const coordinate = requestedCoordinate ?? coordinateIds[0];
149
+ const definition = findCoordinate(program, coordinate);
150
+ if (definition?.type !== "cartesian") {
151
+ throw new Error(`${direction} grid requires a Cartesian coordinate.`);
152
+ }
153
+ const scaleIds = [...new Set(layers.map(layer => layer.encoding[channel].scale))];
154
+ if (scaleIds.length === 0) throw new Error(`Unknown grid scale "${requestedScale}".`);
155
+ if (scaleIds.length > 1) {
156
+ throw new Error(`${direction} grid found multiple ${channel} scales; provide scale explicitly.`);
157
+ }
158
+ const scale = requestedScale ?? scaleIds[0];
159
+ if (
160
+ !["linear", "time"].includes(program.resolvedScales[scale]?.type) &&
161
+ !isTransformedScaleType(program.resolvedScales[scale]?.type)
162
+ ) {
163
+ throw new Error(`${direction} grid requires resolved continuous scale "${scale}".`);
164
+ }
165
+ const related = new Set(layers.map(layer => layer.id));
166
+ let before;
167
+ walkGraphicDrawOrder(program.graphicSpec, ({ id }) => {
168
+ if (before === undefined && related.has(id)) before = id;
169
+ });
170
+ if (before === undefined) throw new Error(`${direction} grid requires related mark graphics.`);
171
+ const owner = findGraphicParent(program.graphicSpec, before);
172
+ return {
173
+ channel,
174
+ coordinate,
175
+ scale,
176
+ before,
177
+ ...(owner?.kind === "parent" ? { parent: owner.id } : {})
178
+ };
179
+ }
180
+
181
+ export function resolveGridConfig(program, direction, args, resources) {
182
+ const hasExplicitValues = Object.hasOwn(args, "values") ||
183
+ Object.hasOwn(args, "count");
184
+ const explicit = Object.hasOwn(args, "values")
185
+ ? { mode: "values", values: args.values }
186
+ : Object.hasOwn(args, "count")
187
+ ? { mode: "count", count: args.count }
188
+ : inferGridTickConfig(program, resources.channel, resources.scale);
189
+ return {
190
+ direction,
191
+ scale: resources.scale,
192
+ coordinate: resources.coordinate,
193
+ ...explicit,
194
+ inferredValues: !hasExplicitValues,
195
+ color: args.color ?? DEFAULT_STYLE.color,
196
+ lineWidth: args.lineWidth ?? DEFAULT_STYLE.lineWidth,
197
+ strokeDash: args.strokeDash ?? DEFAULT_STYLE.strokeDash
198
+ };
199
+ }
200
+
201
+ export function refreshGridConfig(program, config) {
202
+ if (config.inferredValues !== true) return config;
203
+ const channel = config.direction === "horizontal" ? "y" : "x";
204
+ const refreshed = {
205
+ ...config,
206
+ ...inferGridTickConfig(program, channel, config.scale)
207
+ };
208
+ if (refreshed.mode === "values") delete refreshed.count;
209
+ else delete refreshed.values;
210
+ return refreshed;
211
+ }
212
+
213
+ export function editGridConfig(previous, args) {
214
+ const config = {
215
+ ...previous,
216
+ ...Object.fromEntries(
217
+ Object.entries(args).filter(([key]) => !["count", "values"].includes(key))
218
+ )
219
+ };
220
+ if (Object.hasOwn(args, "count")) {
221
+ config.mode = "count";
222
+ config.count = args.count;
223
+ config.inferredValues = false;
224
+ delete config.values;
225
+ } else if (Array.isArray(args.values)) {
226
+ config.mode = "values";
227
+ config.values = args.values;
228
+ config.inferredValues = false;
229
+ delete config.count;
230
+ } else if (args.values === "auto") {
231
+ config.inferredValues = true;
232
+ }
233
+ return config;
234
+ }
235
+
236
+ export function resolveGridGeometry(program, config) {
237
+ const scale = program.resolvedScales[config.scale];
238
+ const bounds = resolveGraphicBounds(program);
239
+ if ((
240
+ !["linear", "time"].includes(scale?.type) &&
241
+ !isTransformedScaleType(scale?.type)
242
+ ) || bounds === undefined) {
243
+ throw new Error("Grid materialization requires a continuous scale and Canvas bounds.");
244
+ }
245
+ const values = valuesFromTickConfig(program, config);
246
+ const low = Math.min(...scale.domain);
247
+ const high = Math.max(...scale.domain);
248
+ if (!values.every(value => value >= low && value <= high)) {
249
+ throw new RangeError("Grid values must be inside the scale domain.");
250
+ }
251
+ const positions = mapContinuousScaleValues(values, scale);
252
+ return resolveGridLineGeometry({
253
+ direction: config.direction,
254
+ values,
255
+ positions,
256
+ bounds
257
+ });
258
+ }
@@ -0,0 +1,138 @@
1
+ import { action } from "../../core/action.js";
2
+ import { isPlainObject } from "../../core/immutable.js";
3
+ import {
4
+ BAR_ORIENTATIONS,
5
+ resolveBarOrientation
6
+ } from "../../grammar/bars/policy.js";
7
+ import { findSemanticScale } from "../../selectors/scales.js";
8
+
9
+ const OPTIONS = Object.freeze(["axes", "grid", "legend"]);
10
+
11
+ function validateGuideOption(value, label) {
12
+ if (value !== undefined && value !== false && !isPlainObject(value)) {
13
+ throw new TypeError(`${label} must be false or a plain object.`);
14
+ }
15
+ }
16
+
17
+ function validateOptions(args) {
18
+ if (!isPlainObject(args)) {
19
+ throw new TypeError("createGuides options must be a plain object.");
20
+ }
21
+ for (const key of Object.keys(args)) {
22
+ if (!OPTIONS.includes(key)) {
23
+ throw new Error(`Unknown createGuides option "${key}".`);
24
+ }
25
+ }
26
+ validateGuideOption(args.axes, "createGuides axes");
27
+ validateGuideOption(args.grid, "createGuides grid");
28
+ validateGuideOption(args.legend, "createGuides legend");
29
+ }
30
+
31
+ function hasCartesianEncoding(program) {
32
+ return program.semanticSpec.layers.some(
33
+ layer => layer.encoding?.x !== undefined || layer.encoding?.y !== undefined
34
+ );
35
+ }
36
+
37
+ function hasGridEncoding(program) {
38
+ return program.semanticSpec.layers.some(
39
+ layer => layer.encoding?.x?.scale !== undefined ||
40
+ layer.encoding?.y?.scale !== undefined
41
+ );
42
+ }
43
+
44
+ function inferGridOptions(program) {
45
+ const barOrientations = program.semanticSpec.layers
46
+ .map(resolveBarOrientation)
47
+ .filter(Boolean);
48
+ const positionedLayers = program.semanticSpec.layers.filter(
49
+ layer => layer.encoding?.x !== undefined && layer.encoding?.y !== undefined
50
+ );
51
+ const horizontalQuantitative = positionedLayers.length > 0 &&
52
+ positionedLayers.every(layer =>
53
+ layer.encoding.x.fieldType === "quantitative" &&
54
+ ["nominal", "ordinal", "temporal"].includes(layer.encoding.y.fieldType)
55
+ );
56
+ if (
57
+ horizontalQuantitative ||
58
+ (barOrientations.length > 0 &&
59
+ barOrientations.every(value => value === BAR_ORIENTATIONS.horizontal))
60
+ ) {
61
+ return { horizontal: false, vertical: true };
62
+ }
63
+ return { horizontal: true, vertical: false };
64
+ }
65
+
66
+ function inferAxesOptions(program) {
67
+ const horizontalInterval = program.semanticSpec.layers.some(layer =>
68
+ layer.mark?.type === "rule" &&
69
+ layer.encoding?.x?.fieldType === "quantitative" &&
70
+ layer.encoding?.x2?.fieldType === "quantitative" &&
71
+ ["nominal", "ordinal", "temporal"].includes(layer.encoding?.y?.fieldType)
72
+ );
73
+ return horizontalInterval
74
+ ? { x: { ticksAndLabels: { count: 7 } } }
75
+ : {};
76
+ }
77
+
78
+ function hasLegendEncoding(program) {
79
+ return program.semanticSpec.layers.some(
80
+ layer =>
81
+ (layer.mark?.type === "point" &&
82
+ layer.encoding?.opacity?.scale !== undefined) ||
83
+ (layer.mark?.type === "point" &&
84
+ layer.encoding?.color?.scale !== undefined &&
85
+ (layer.encoding?.shape?.scale !== undefined ||
86
+ ["sequential", "quantize", "quantile", "threshold"].includes(
87
+ findSemanticScale(program, layer.encoding.color.scale)?.type
88
+ ))) ||
89
+ (layer.mark?.type === "line" &&
90
+ ["color", "strokeDash"].some(
91
+ channel => layer.encoding?.[channel]?.scale !== undefined
92
+ )) ||
93
+ (layer.mark?.type === "bar" &&
94
+ layer.encoding?.color?.scale !== undefined) ||
95
+ (layer.mark?.type === "area" &&
96
+ layer.encoding?.color?.scale !== undefined)
97
+ );
98
+ }
99
+
100
+ function selectOption(explicit, applicable) {
101
+ if (explicit === false) return undefined;
102
+ if (explicit !== undefined) return explicit;
103
+ return applicable ? {} : undefined;
104
+ }
105
+
106
+ const createGuides = action(
107
+ {
108
+ op: "createGuides",
109
+ description: "Create applicable axes, grid, and legend."
110
+ },
111
+ function (args = {}) {
112
+ validateOptions(args);
113
+ const axes = args.axes === undefined && hasCartesianEncoding(this)
114
+ ? inferAxesOptions(this)
115
+ : selectOption(args.axes, hasCartesianEncoding(this));
116
+ const grid = args.grid === undefined && hasGridEncoding(this)
117
+ ? inferGridOptions(this)
118
+ : selectOption(args.grid, hasGridEncoding(this));
119
+ const legend = selectOption(
120
+ args.legend,
121
+ hasLegendEncoding(this)
122
+ );
123
+
124
+ if (axes === undefined && grid === undefined && legend === undefined) {
125
+ throw new Error("createGuides requires at least one selected guide.");
126
+ }
127
+
128
+ let next = this;
129
+ if (axes !== undefined) next = next.createAxes(axes);
130
+ if (grid !== undefined) next = next.createGrid(grid);
131
+ if (legend !== undefined) next = next.createLegend(legend);
132
+ return next;
133
+ }
134
+ );
135
+
136
+ export function registerGuideCollectionActions(ProgramClass) {
137
+ ProgramClass.prototype.createGuides = createGuides;
138
+ }
@@ -0,0 +1,11 @@
1
+ import { registerGuideAxisActions } from "./axes/index.js";
2
+ import { registerGuideCollectionActions } from "./guides.js";
3
+ import { registerGridActions } from "./grids/index.js";
4
+ import { registerLegendActions } from "./legends/index.js";
5
+
6
+ export function registerGuideActions(ProgramClass) {
7
+ registerGuideAxisActions(ProgramClass);
8
+ registerLegendActions(ProgramClass);
9
+ registerGridActions(ProgramClass);
10
+ registerGuideCollectionActions(ProgramClass);
11
+ }