ggaction 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +12 -1
  3. package/package.json +6 -1
  4. package/src/actions/boxPlots/components.js +3 -3
  5. package/src/actions/boxPlots/edit.js +277 -0
  6. package/src/actions/boxPlots/index.js +2 -0
  7. package/src/actions/boxPlots/materialize.js +12 -1
  8. package/src/actions/boxPlots/options.js +25 -14
  9. package/src/actions/canvas/actions.js +21 -17
  10. package/src/actions/composition/actions.js +247 -0
  11. package/src/actions/composition/index.js +1 -0
  12. package/src/actions/coordinates/actions.js +2 -5
  13. package/src/actions/data/density.js +7 -1
  14. package/src/actions/data/filter.js +3 -1
  15. package/src/actions/encodings/appearance.js +11 -0
  16. package/src/actions/encodings/barWidth.js +5 -3
  17. package/src/actions/encodings/color.js +120 -25
  18. package/src/actions/encodings/index.js +2 -0
  19. package/src/actions/encodings/offset.js +90 -72
  20. package/src/actions/encodings/position/apply.js +66 -0
  21. package/src/actions/encodings/position/policies/arc.js +26 -0
  22. package/src/actions/encodings/position/policies/bar.js +9 -5
  23. package/src/actions/encodings/position/policies/index.js +5 -1
  24. package/src/actions/encodings/position/policies/line.js +20 -3
  25. package/src/actions/encodings/position/resolve.js +57 -8
  26. package/src/actions/encodings/position.js +44 -90
  27. package/src/actions/encodings/ranged.js +28 -6
  28. package/src/actions/encodings/shared.js +17 -21
  29. package/src/actions/encodings/text.js +73 -0
  30. package/src/actions/errorBands/create.js +37 -29
  31. package/src/actions/errorBands/edit.js +206 -0
  32. package/src/actions/errorBands/index.js +9 -0
  33. package/src/actions/errorBands/options.js +39 -0
  34. package/src/actions/errorBars/create.js +50 -43
  35. package/src/actions/errorBars/edit.js +130 -0
  36. package/src/actions/errorBars/index.js +3 -0
  37. package/src/actions/errorBars/options.js +52 -0
  38. package/src/actions/facets/actions.js +162 -0
  39. package/src/actions/facets/derive.js +200 -0
  40. package/src/actions/facets/guides.js +16 -0
  41. package/src/actions/facets/index.js +1 -0
  42. package/src/actions/facets/replay.js +69 -0
  43. package/src/actions/guides/applicability.js +113 -0
  44. package/src/actions/guides/axes/axes.js +70 -16
  45. package/src/actions/guides/axes/edit.js +126 -0
  46. package/src/actions/guides/axes/index.js +12 -0
  47. package/src/actions/guides/axes/labels.js +21 -18
  48. package/src/actions/guides/axes/remove.js +60 -0
  49. package/src/actions/guides/axes/tickGroups.js +7 -5
  50. package/src/actions/guides/axes/ticks.js +9 -4
  51. package/src/actions/guides/axes/titles.js +7 -5
  52. package/src/actions/guides/grids/grid.js +144 -15
  53. package/src/actions/guides/grids/resolve.js +5 -13
  54. package/src/actions/guides/guides.js +17 -75
  55. package/src/actions/guides/index.js +6 -0
  56. package/src/actions/guides/legends/categorical/actions.js +48 -9
  57. package/src/actions/guides/legends/categorical/components.js +16 -16
  58. package/src/actions/guides/legends/categorical/index.js +14 -14
  59. package/src/actions/guides/legends/categorical/layout.js +3 -1
  60. package/src/actions/guides/legends/categorical/options.js +2 -5
  61. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  62. package/src/actions/guides/legends/categorical/symbols.js +14 -14
  63. package/src/actions/guides/legends/continuous/common.js +7 -7
  64. package/src/actions/guides/legends/continuous/interval.js +5 -5
  65. package/src/actions/guides/legends/edit.js +2 -6
  66. package/src/actions/guides/legends/focused.js +63 -0
  67. package/src/actions/guides/legends/index.js +4 -0
  68. package/src/actions/guides/legends/remove.js +64 -0
  69. package/src/actions/guides/legends/size.js +12 -14
  70. package/src/actions/guides/polar/axes/facade.js +219 -0
  71. package/src/actions/guides/polar/axes/labels.js +188 -0
  72. package/src/actions/guides/polar/axes/lines.js +113 -0
  73. package/src/actions/guides/polar/axes/shared.js +112 -0
  74. package/src/actions/guides/polar/axes/ticks.js +164 -0
  75. package/src/actions/guides/polar/axes/titles.js +165 -0
  76. package/src/actions/guides/polar/axes.js +55 -0
  77. package/src/actions/guides/polar/grids.js +252 -0
  78. package/src/actions/guides/polar/index.js +2 -0
  79. package/src/actions/guides/polar/resolve.js +269 -0
  80. package/src/actions/index.js +4 -0
  81. package/src/actions/marks/arc/actions.js +239 -0
  82. package/src/actions/marks/arc/index.js +1 -0
  83. package/src/actions/marks/{area.js → area/actions.js} +51 -38
  84. package/src/actions/marks/area/index.js +4 -0
  85. package/src/actions/marks/bar/create.js +26 -5
  86. package/src/actions/marks/bar/materialize.js +25 -31
  87. package/src/actions/marks/index.js +12 -4
  88. package/src/actions/marks/lifecycle.js +27 -0
  89. package/src/actions/marks/line/actions.js +373 -0
  90. package/src/actions/marks/line/index.js +1 -0
  91. package/src/actions/marks/{point.js → point/actions.js} +67 -108
  92. package/src/actions/marks/point/index.js +1 -0
  93. package/src/actions/marks/rect/actions.js +165 -0
  94. package/src/actions/marks/rect/index.js +1 -0
  95. package/src/actions/marks/remove.js +214 -0
  96. package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
  97. package/src/actions/marks/rule/index.js +1 -0
  98. package/src/actions/marks/shared.js +156 -25
  99. package/src/actions/marks/text/actions.js +192 -0
  100. package/src/actions/marks/text/index.js +1 -0
  101. package/src/actions/primitives/createGraphics.js +13 -6
  102. package/src/actions/primitives/editGraphics.js +10 -3
  103. package/src/actions/primitives/semantic.js +26 -3
  104. package/src/actions/primitives/semanticValue.js +25 -3
  105. package/src/actions/regression/components.js +1 -1
  106. package/src/actions/regression/create.js +18 -1
  107. package/src/actions/regression/edit.js +247 -0
  108. package/src/actions/regression/index.js +3 -0
  109. package/src/actions/regression/resolve.js +3 -1
  110. package/src/actions/scales/consumers.js +15 -6
  111. package/src/actions/scales/definitions.js +13 -4
  112. package/src/actions/scales/edit.js +42 -19
  113. package/src/actions/scales/materialize.js +57 -408
  114. package/src/actions/selection/actions.js +38 -24
  115. package/src/actions/selection/index.js +2 -0
  116. package/src/actions/titles/actions.js +63 -2
  117. package/src/actions/titles/index.js +2 -0
  118. package/src/actions/titles/resolve.js +12 -17
  119. package/src/composition.js +10 -0
  120. package/src/core/ChartProgram.js +200 -2
  121. package/src/core/action.js +10 -0
  122. package/src/core/validation.js +16 -0
  123. package/src/core/vocabulary.js +154 -4
  124. package/src/grammar/arcs.js +154 -0
  125. package/src/grammar/bars/aggregate.js +2 -2
  126. package/src/grammar/bars/geometry.js +3 -3
  127. package/src/grammar/bars/policy.js +7 -1
  128. package/src/grammar/coordinates.js +3 -7
  129. package/src/grammar/density.js +20 -1
  130. package/src/grammar/facets/dependencies.js +185 -0
  131. package/src/grammar/facets/guides.js +257 -0
  132. package/src/grammar/facets/scales.js +254 -0
  133. package/src/grammar/facets.js +122 -0
  134. package/src/grammar/lineSeries.js +111 -3
  135. package/src/grammar/polar.js +102 -0
  136. package/src/grammar/polarGuides.js +243 -0
  137. package/src/grammar/polarLineCommands.js +43 -0
  138. package/src/grammar/polarPaths.js +161 -0
  139. package/src/grammar/positionCompatibility.js +18 -2
  140. package/src/grammar/rects.js +26 -0
  141. package/src/grammar/scales/continuous.js +6 -1
  142. package/src/grammar/scales/fields.js +7 -0
  143. package/src/grammar/scales/ordinal.js +5 -2
  144. package/src/grammar/scales/policies.js +1 -1
  145. package/src/grammar/schemas/graphic.js +1 -1
  146. package/src/grammar/schemas/graphicBounds.js +64 -9
  147. package/src/grammar/schemas/graphicTree.js +44 -10
  148. package/src/grammar/schemas/semanticPath.js +31 -9
  149. package/src/grammar/seriesLayout.js +13 -2
  150. package/src/grammar/text.js +71 -0
  151. package/src/grammar/ticks.js +50 -13
  152. package/src/index.js +1 -0
  153. package/src/layout/composition.js +310 -0
  154. package/src/layout/facets.js +131 -0
  155. package/src/layout/title.js +6 -0
  156. package/src/materialization/bars/aggregate.js +5 -2
  157. package/src/materialization/bars/grouped.js +75 -37
  158. package/src/materialization/composition.js +152 -0
  159. package/src/materialization/compositionSnapshot.js +114 -0
  160. package/src/materialization/dependencies.js +71 -16
  161. package/src/materialization/encodings.js +14 -28
  162. package/src/materialization/facetGuides.js +426 -0
  163. package/src/materialization/facets.js +214 -0
  164. package/src/materialization/graphicHierarchy.js +15 -5
  165. package/src/materialization/guides/resources.js +46 -0
  166. package/src/materialization/marks.js +220 -14
  167. package/src/materialization/planner.js +105 -12
  168. package/src/materialization/rect.js +138 -0
  169. package/src/materialization/rectConfig.js +38 -0
  170. package/src/materialization/rowEncoding.js +33 -0
  171. package/src/materialization/scales/map.js +40 -0
  172. package/src/materialization/scales/policies/arc.js +37 -0
  173. package/src/materialization/scales/policies/bar.js +132 -0
  174. package/src/materialization/scales/policies/series.js +76 -0
  175. package/src/materialization/scales/resolve.js +326 -0
  176. package/src/materialization/selection/items/arc.js +29 -0
  177. package/src/materialization/selection/items/bar.js +255 -0
  178. package/src/materialization/selection/items/common.js +131 -0
  179. package/src/materialization/selection/items/index.js +6 -0
  180. package/src/materialization/selection/items/path.js +59 -0
  181. package/src/materialization/selection/items/point.js +55 -0
  182. package/src/materialization/selection/items/rect.js +23 -0
  183. package/src/materialization/selection/items/rule.js +25 -0
  184. package/src/materialization/selection/policies/arc.js +10 -0
  185. package/src/materialization/selection/policies/area.js +1 -1
  186. package/src/materialization/selection/policies/bar.js +1 -1
  187. package/src/materialization/selection/policies/index.js +5 -1
  188. package/src/materialization/selection/policies/line.js +1 -1
  189. package/src/materialization/selection/policies/point.js +1 -1
  190. package/src/materialization/selection/policies/rect.js +10 -0
  191. package/src/materialization/selection/policies/rule.js +1 -1
  192. package/src/materialization/selection/styles.js +24 -16
  193. package/src/materialization/text.js +156 -0
  194. package/src/renderers/canvas/index.js +68 -22
  195. package/types/extension.d.ts +1 -0
  196. package/types/index.d.ts +56 -1
  197. package/types/program.d.ts +664 -27
  198. package/src/actions/marks/line.js +0 -248
  199. package/src/materialization/selection/items.js +0 -503
@@ -8,19 +8,19 @@ import {
8
8
  createLegendBackground,
9
9
  createLegendLabels,
10
10
  createLegendTitle,
11
- editLegendBackground,
12
- editLegendLabels,
13
- editLegendTitle
11
+ rematerializeLegendBackground,
12
+ rematerializeLegendLabels,
13
+ rematerializeLegendTitle
14
14
  } from "./components.js";
15
15
  import {
16
16
  createLegendSymbolLines,
17
17
  createLegendSymbolPoints,
18
18
  createLegendSymbolSwatches,
19
19
  createLegendSymbols,
20
- editLegendSymbolLines,
21
- editLegendSymbolPoints,
22
- editLegendSymbolSwatches,
23
- editLegendSymbols,
20
+ rematerializeLegendSymbolLines,
21
+ rematerializeLegendSymbolPoints,
22
+ rematerializeLegendSymbolSwatches,
23
+ rematerializeLegendSymbols,
24
24
  rematerializeLegendHighlights
25
25
  } from "./symbols.js";
26
26
  import { editLegend } from "../edit.js";
@@ -31,19 +31,19 @@ export function registerCategoricalLegendActions(ProgramClass) {
31
31
  ProgramClass.prototype.createCategoricalLegend = createCategoricalLegend;
32
32
  ProgramClass.prototype.removeCategoricalLegend = removeCategoricalLegend;
33
33
  ProgramClass.prototype.createLegendBackground = createLegendBackground;
34
- ProgramClass.prototype.editLegendBackground = editLegendBackground;
34
+ ProgramClass.prototype.rematerializeLegendBackground = rematerializeLegendBackground;
35
35
  ProgramClass.prototype.createLegendSymbols = createLegendSymbols;
36
- ProgramClass.prototype.editLegendSymbols = editLegendSymbols;
36
+ ProgramClass.prototype.rematerializeLegendSymbols = rematerializeLegendSymbols;
37
37
  ProgramClass.prototype.rematerializeLegendHighlights = rematerializeLegendHighlights;
38
38
  ProgramClass.prototype.createLegendSymbolLines = createLegendSymbolLines;
39
- ProgramClass.prototype.editLegendSymbolLines = editLegendSymbolLines;
39
+ ProgramClass.prototype.rematerializeLegendSymbolLines = rematerializeLegendSymbolLines;
40
40
  ProgramClass.prototype.createLegendSymbolPoints = createLegendSymbolPoints;
41
- ProgramClass.prototype.editLegendSymbolPoints = editLegendSymbolPoints;
41
+ ProgramClass.prototype.rematerializeLegendSymbolPoints = rematerializeLegendSymbolPoints;
42
42
  ProgramClass.prototype.createLegendSymbolSwatches = createLegendSymbolSwatches;
43
- ProgramClass.prototype.editLegendSymbolSwatches = editLegendSymbolSwatches;
43
+ ProgramClass.prototype.rematerializeLegendSymbolSwatches = rematerializeLegendSymbolSwatches;
44
44
  ProgramClass.prototype.createLegendLabels = createLegendLabels;
45
- ProgramClass.prototype.editLegendLabels = editLegendLabels;
45
+ ProgramClass.prototype.rematerializeLegendLabels = rematerializeLegendLabels;
46
46
  ProgramClass.prototype.createLegendTitle = createLegendTitle;
47
- ProgramClass.prototype.editLegendTitle = editLegendTitle;
47
+ ProgramClass.prototype.rematerializeLegendTitle = rematerializeLegendTitle;
48
48
  ProgramClass.prototype.rematerializeLegend = rematerializeLegend;
49
49
  }
@@ -10,6 +10,8 @@ import {
10
10
  measureLegendTextWidth,
11
11
  resolveLegendGrid
12
12
  } from "../../../../layout/legend.js";
13
+ import { findCanvasGraphic } from
14
+ "../../../../materialization/graphicHierarchy.js";
13
15
 
14
16
  export function activeConfig(program) {
15
17
  const entries = ["series", "color"]
@@ -366,7 +368,7 @@ function resolveCompactBottomLayout(bounds, canvas, config, width, count) {
366
368
 
367
369
  export function resolveLayout(program, config) {
368
370
  const bounds = resolveGraphicBounds(program);
369
- const canvas = program.graphicSpec.objects.canvas;
371
+ const canvas = findCanvasGraphic(program);
370
372
  if (
371
373
  bounds === undefined ||
372
374
  ![bounds.x, bounds.y, bounds.width, bounds.height].every(Number.isFinite) ||
@@ -1,4 +1,4 @@
1
- import { isPlainObject } from "../../../../core/immutable.js";
1
+ import { validateOptionObject } from "../../../../core/validation.js";
2
2
  import { CATEGORICAL_LEGEND_CHANNELS } from "../../../../core/vocabulary.js";
3
3
  import { normalizeRecipe } from "./recipes.js";
4
4
  import {
@@ -86,10 +86,7 @@ function normalizeBorder(border) {
86
86
  }
87
87
 
88
88
  export function normalizeOptions(args, kind) {
89
- if (!isPlainObject(args)) {
90
- throw new TypeError("createLegend options must be a plain object.");
91
- }
92
- validateKeys(args, OPTIONS, "createLegend");
89
+ validateOptionObject(args, OPTIONS, "createLegend");
93
90
  if (Object.hasOwn(args, "labels")) {
94
91
  validateObject(args.labels, TEXT_OPTIONS, "createLegend.labels");
95
92
  }
@@ -11,7 +11,7 @@ function isCategoricalTarget(layer) {
11
11
  channel => layer.encoding?.[channel]?.scale !== undefined
12
12
  );
13
13
  }
14
- return ["bar", "area"].includes(layer?.mark?.type) &&
14
+ return ["bar", "area", "arc", "rect"].includes(layer?.mark?.type) &&
15
15
  layer.encoding?.color?.scale !== undefined;
16
16
  }
17
17
 
@@ -39,7 +39,7 @@ export function resolveTarget(program, requested) {
39
39
  export const sameValues = sameOrderedValues;
40
40
 
41
41
  export function resolveLegendKind(layer, requestedChannels) {
42
- if (["bar", "area"].includes(layer.mark.type)) return "color";
42
+ if (["bar", "area", "arc", "rect"].includes(layer.mark.type)) return "color";
43
43
  if (
44
44
  layer.mark.type === "point" &&
45
45
  sameValues(requestedChannels, ["color"])
@@ -53,7 +53,7 @@ function legendLayerStyle(layer, style) {
53
53
 
54
54
  function makeEditSymbol(type) {
55
55
  const suffix = { line: "Lines", point: "Points", swatch: "Swatches" }[type];
56
- const op = `editLegendSymbol${suffix}`;
56
+ const op = `rematerializeLegendSymbol${suffix}`;
57
57
  return action(
58
58
  { op, description: `Rematerialize categorical legend ${type} symbols.` },
59
59
  function (args = {}) {
@@ -180,20 +180,20 @@ function makeCreateSymbol(type, edit) {
180
180
  );
181
181
  }
182
182
 
183
- export const editLegendSymbolLines = makeEditSymbol("line");
184
- export const editLegendSymbolPoints = makeEditSymbol("point");
185
- export const editLegendSymbolSwatches = makeEditSymbol("swatch");
183
+ export const rematerializeLegendSymbolLines = makeEditSymbol("line");
184
+ export const rematerializeLegendSymbolPoints = makeEditSymbol("point");
185
+ export const rematerializeLegendSymbolSwatches = makeEditSymbol("swatch");
186
186
  export const createLegendSymbolLines = makeCreateSymbol(
187
187
  "line",
188
- "editLegendSymbolLines"
188
+ "rematerializeLegendSymbolLines"
189
189
  );
190
190
  export const createLegendSymbolPoints = makeCreateSymbol(
191
191
  "point",
192
- "editLegendSymbolPoints"
192
+ "rematerializeLegendSymbolPoints"
193
193
  );
194
194
  export const createLegendSymbolSwatches = makeCreateSymbol(
195
195
  "swatch",
196
- "editLegendSymbolSwatches"
196
+ "rematerializeLegendSymbolSwatches"
197
197
  );
198
198
 
199
199
  export const createLegendSymbols = action(
@@ -214,17 +214,17 @@ export const createLegendSymbols = action(
214
214
  }
215
215
  );
216
216
 
217
- export const editLegendSymbols = action(
218
- { op: "editLegendSymbols", description: "Rematerialize layered legend symbols." },
217
+ export const rematerializeLegendSymbols = action(
218
+ { op: "rematerializeLegendSymbols", description: "Rematerialize layered legend symbols." },
219
219
  function (args = {}) {
220
- noOptions(args, "editLegendSymbols");
220
+ noOptions(args, "rematerializeLegendSymbols");
221
221
  const { config } = activeConfig(this);
222
222
  let next = this;
223
223
  for (const layer of config.symbol.layers) {
224
224
  const operation = {
225
- line: "editLegendSymbolLines",
226
- point: "editLegendSymbolPoints",
227
- swatch: "editLegendSymbolSwatches"
225
+ line: "rematerializeLegendSymbolLines",
226
+ point: "rematerializeLegendSymbolPoints",
227
+ swatch: "rematerializeLegendSymbolSwatches"
228
228
  }[layer.type];
229
229
  next = next[operation]();
230
230
  }
@@ -252,7 +252,7 @@ export const rematerializeLegendHighlights = action(
252
252
  })).filter(entry => entry.states !== undefined);
253
253
  if (highlights.length === 0) return this;
254
254
 
255
- let next = this.editLegendSymbols();
255
+ let next = this.rematerializeLegendSymbols();
256
256
  for (const { highlight, states } of highlights) {
257
257
  for (const layer of config.symbol.layers) {
258
258
  const id = symbolGraphic(config, layer.type);
@@ -1,6 +1,7 @@
1
1
  import { isPlainObject } from "../../../../core/immutable.js";
2
2
  import {
3
3
  validateKeys,
4
+ validateOptionObject,
4
5
  validateNonEmptyString,
5
6
  validateNonNegativeFinite,
6
7
  validatePositiveFinite
@@ -10,6 +11,8 @@ import { resolveGraphicBounds } from "../../../../layout/canvas.js";
10
11
  import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
11
12
  "../../../../theme/defaults.js";
12
13
  import { findLayer } from "../../../../selectors/layers.js";
14
+ import { findCanvasGraphic } from
15
+ "../../../../materialization/graphicHierarchy.js";
13
16
 
14
17
  const OPTIONS = Object.freeze([
15
18
  "target", "channels", "position", "align", "offset", "title", "count",
@@ -85,10 +88,7 @@ function normalizeBorder(value) {
85
88
  }
86
89
 
87
90
  export function normalizeContinuousLegend(args, kind) {
88
- if (!isPlainObject(args)) {
89
- throw new TypeError("createLegend options must be a plain object.");
90
- }
91
- validateKeys(args, OPTIONS, "createLegend");
91
+ validateOptionObject(args, OPTIONS, "createLegend");
92
92
  const position = args.position ?? "right";
93
93
  if (!POSITIONS.includes(position)) {
94
94
  throw new Error(`Unsupported legend position "${position}".`);
@@ -170,7 +170,7 @@ export function resolveContinuousPoint(program, requested, channel) {
170
170
 
171
171
  export function resolveContinuousColorLayer(program, requested) {
172
172
  const candidates = program.semanticSpec.layers.filter(layer =>
173
- ["point", "bar"].includes(layer.mark?.type) &&
173
+ ["point", "bar", "rect"].includes(layer.mark?.type) &&
174
174
  layer.encoding?.color?.scale !== undefined
175
175
  );
176
176
  const layer = requested === undefined
@@ -182,7 +182,7 @@ export function resolveContinuousColorLayer(program, requested) {
182
182
  if (layer === undefined) {
183
183
  throw new Error(
184
184
  requested === undefined
185
- ? "color legend requires one eligible point or bar mark."
185
+ ? "color legend requires one eligible point, bar, or rect mark."
186
186
  : `Unknown color legend target "${requested}".`
187
187
  );
188
188
  }
@@ -199,7 +199,7 @@ export function requireResolvedLegendScale(program, id, type) {
199
199
 
200
200
  export function resolveContinuousBounds(program) {
201
201
  const plot = resolveGraphicBounds(program);
202
- const canvas = program.graphicSpec.objects.canvas;
202
+ const canvas = findCanvasGraphic(program);
203
203
  if (
204
204
  plot === undefined ||
205
205
  ![plot.x, plot.y, plot.width, plot.height].every(Number.isFinite) ||
@@ -1,6 +1,9 @@
1
1
  import { action } from "../../../../core/action.js";
2
2
  import { isPlainObject } from "../../../../core/immutable.js";
3
- import { validateKeys } from "../../../../core/validation.js";
3
+ import {
4
+ validateKeys,
5
+ validateOptionObject
6
+ } from "../../../../core/validation.js";
4
7
  import { formatDiscretizedIntervals } from "../../../../grammar/scales.js";
5
8
  import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
6
9
  "../../../../theme/defaults.js";
@@ -38,10 +41,7 @@ function textOptions(value, label, defaults) {
38
41
  }
39
42
 
40
43
  export function normalizeIntervalLegend(args) {
41
- if (!isPlainObject(args)) {
42
- throw new TypeError("createLegend options must be a plain object.");
43
- }
44
- validateKeys(args, OPTIONS, "createLegend");
44
+ validateOptionObject(args, OPTIONS, "createLegend");
45
45
  if (args.channels !== undefined && (
46
46
  !Array.isArray(args.channels) ||
47
47
  args.channels.length !== 1 ||
@@ -1,7 +1,6 @@
1
1
  import { action } from "../../../core/action.js";
2
2
  import { validateUserId } from "../../../core/identifiers.js";
3
- import { isPlainObject } from "../../../core/immutable.js";
4
- import { validateKeys } from "../../../core/validation.js";
3
+ import { validateOptionObject } from "../../../core/validation.js";
5
4
  import { normalizeOptions } from "./categorical/options.js";
6
5
  import { symbolGraphic } from "./categorical/layout.js";
7
6
  import {
@@ -276,10 +275,7 @@ function editCategorical(program, kind, previous, size, args) {
276
275
  export const editLegend = action(
277
276
  { op: "editLegend", description: "Edit one stable legend layout or appearance." },
278
277
  function (args = {}) {
279
- if (!isPlainObject(args)) {
280
- throw new TypeError("editLegend options must be a plain object.");
281
- }
282
- validateKeys(args, OPTIONS, "editLegend");
278
+ validateOptionObject(args, OPTIONS, "editLegend");
283
279
  const changes = Object.keys(args).filter(key => key !== "target");
284
280
  if (changes.length === 0) {
285
281
  throw new Error("editLegend requires at least one change.");
@@ -0,0 +1,63 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { validateOptionObject } from "../../../core/validation.js";
3
+
4
+ function makeFocusedLegendEdit({ name, options, map }) {
5
+ return action(
6
+ {
7
+ op: name,
8
+ description: `Edit one focused legend component through editLegend.`
9
+ },
10
+ function (args = {}) {
11
+ validateOptionObject(args, ["target", ...options], name);
12
+ if (!options.some(option => Object.hasOwn(args, option))) {
13
+ throw new Error(`${name} requires at least one component change.`);
14
+ }
15
+ return this.editLegend(map(args));
16
+ }
17
+ );
18
+ }
19
+
20
+ const editLegendLayout = makeFocusedLegendEdit({
21
+ name: "editLegendLayout",
22
+ options: [
23
+ "position", "align", "direction", "columns", "offset",
24
+ "titlePosition", "itemGap"
25
+ ],
26
+ map: args => args
27
+ });
28
+
29
+ const editLegendLabels = makeFocusedLegendEdit({
30
+ name: "editLegendLabels",
31
+ options: ["color", "fontSize", "fontFamily", "fontWeight"],
32
+ map: ({ target, ...labels }) => ({ target, labels })
33
+ });
34
+
35
+ const editLegendTitle = makeFocusedLegendEdit({
36
+ name: "editLegendTitle",
37
+ options: ["title", "color", "fontSize", "fontFamily", "fontWeight"],
38
+ map: ({ target, title, ...titleStyle }) => ({
39
+ target,
40
+ ...(title === undefined ? {} : { title }),
41
+ ...(Object.keys(titleStyle).length === 0 ? {} : { titleStyle })
42
+ })
43
+ });
44
+
45
+ const editLegendSymbols = makeFocusedLegendEdit({
46
+ name: "editLegendSymbols",
47
+ options: ["symbol", "count", "gradient"],
48
+ map: args => args
49
+ });
50
+
51
+ const editLegendBorder = makeFocusedLegendEdit({
52
+ name: "editLegendBorder",
53
+ options: ["border"],
54
+ map: args => args
55
+ });
56
+
57
+ export function registerFocusedLegendActions(ProgramClass) {
58
+ ProgramClass.prototype.editLegendLayout = editLegendLayout;
59
+ ProgramClass.prototype.editLegendLabels = editLegendLabels;
60
+ ProgramClass.prototype.editLegendTitle = editLegendTitle;
61
+ ProgramClass.prototype.editLegendSymbols = editLegendSymbols;
62
+ ProgramClass.prototype.editLegendBorder = editLegendBorder;
63
+ }
@@ -1,9 +1,13 @@
1
1
  import { registerCategoricalLegendActions } from "./categorical/index.js";
2
2
  import { registerContinuousLegendActions } from "./continuous/index.js";
3
3
  import { registerSizeLegendActions } from "./size.js";
4
+ import { registerFocusedLegendActions } from "./focused.js";
5
+ import { removeLegend } from "./remove.js";
4
6
 
5
7
  export function registerLegendActions(ProgramClass) {
6
8
  registerCategoricalLegendActions(ProgramClass);
7
9
  registerContinuousLegendActions(ProgramClass);
8
10
  registerSizeLegendActions(ProgramClass);
11
+ registerFocusedLegendActions(ProgramClass);
12
+ ProgramClass.prototype.removeLegend = removeLegend;
9
13
  }
@@ -0,0 +1,64 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { validateUserId } from "../../../core/identifiers.js";
3
+ import { validateKeys } from "../../../core/validation.js";
4
+ import { legendGraphicIds } from
5
+ "../../../materialization/guides/resources.js";
6
+
7
+ const OPTIONS = Object.freeze(["target"]);
8
+
9
+ function resolveTarget(program, requested) {
10
+ const targets = [...new Set(Object.values(program.guideConfigs.legend ?? {})
11
+ .map(config => config?.target)
12
+ .filter(Boolean))];
13
+ if (requested !== undefined) {
14
+ const target = validateUserId(requested, "Legend target id");
15
+ if (!targets.includes(target)) {
16
+ throw new Error(`Unknown legend target "${target}".`);
17
+ }
18
+ return target;
19
+ }
20
+ if (targets.length === 0) throw new Error("removeLegend requires an existing legend.");
21
+ if (targets.length > 1) {
22
+ throw new Error("removeLegend requires target when the legend is ambiguous.");
23
+ }
24
+ return targets[0];
25
+ }
26
+
27
+ const SEMANTIC_KIND = Object.freeze({
28
+ series: "series",
29
+ color: "color",
30
+ size: "size",
31
+ gradient: "color",
32
+ interval: "color",
33
+ opacity: "opacity"
34
+ });
35
+
36
+ export const removeLegend = action(
37
+ { op: "removeLegend", description: "Remove every legend block owned by one mark." },
38
+ function (args = {}) {
39
+ validateKeys(args, OPTIONS, "removeLegend");
40
+ const target = resolveTarget(this, args.target);
41
+ const kinds = Object.entries(this.guideConfigs.legend ?? {})
42
+ .filter(([, config]) => config?.target === target)
43
+ .map(([kind]) => kind);
44
+ const semanticKinds = new Set(kinds.map(kind => SEMANTIC_KIND[kind]));
45
+ let next = this;
46
+ for (const kind of semanticKinds) {
47
+ if (next.semanticSpec.guides.legend?.[kind] !== undefined) {
48
+ next = next.editSemantic({
49
+ property: `guide.legend.${kind}`,
50
+ remove: true
51
+ });
52
+ }
53
+ }
54
+ for (const id of new Set(kinds.flatMap(legendGraphicIds))) {
55
+ if (next.graphicSpec.objects[id] !== undefined) {
56
+ next = next.editGraphics({ target: id, remove: true });
57
+ }
58
+ }
59
+ for (const kind of kinds) {
60
+ next = next._withoutMaterializationConfig(["guides", "legend", kind]);
61
+ }
62
+ return next;
63
+ }
64
+ );
@@ -7,17 +7,18 @@ import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
7
7
  import { resolveLayout as resolveCategoricalLayout } from
8
8
  "./categorical/layout.js";
9
9
  import { findLayer } from "../../../selectors/layers.js";
10
- import { resolveLegendGraphicPlacement } from
10
+ import { findCanvasGraphic, resolveLegendGraphicPlacement } from
11
11
  "../../../materialization/graphicHierarchy.js";
12
12
 
13
13
  const SIZE_OPTIONS = Object.freeze(["target", "count"]);
14
14
 
15
- function resolvePoint(program, requested) {
16
- const candidates = program.semanticSpec.layers.filter(layer =>
17
- layer.mark?.type === "point" &&
18
- layer.encoding?.color?.scale !== undefined &&
19
- layer.encoding?.shape?.scale !== undefined
20
- );
15
+ export function isSizeLegendPoint(layer) {
16
+ return layer?.mark?.type === "point" &&
17
+ layer.encoding?.size?.scale !== undefined;
18
+ }
19
+
20
+ export function resolveSizeLegendPoint(program, requested) {
21
+ const candidates = program.semanticSpec.layers.filter(isSizeLegendPoint);
21
22
  const layer = requested === undefined
22
23
  ? candidates.length === 1 ? candidates[0] : undefined
23
24
  : (() => {
@@ -27,13 +28,10 @@ function resolvePoint(program, requested) {
27
28
  if (layer === undefined) {
28
29
  throw new Error(
29
30
  requested === undefined
30
- ? "Point series legend requires one eligible point mark."
31
- : `Unknown point series legend target "${requested}".`
31
+ ? "Size legend requires one eligible point mark or an explicit target."
32
+ : `Unknown size legend target "${requested}".`
32
33
  );
33
34
  }
34
- if (layer.encoding.color.field !== layer.encoding.shape.field) {
35
- throw new Error("Point color and shape legends require the same field.");
36
- }
37
35
  return layer;
38
36
  }
39
37
 
@@ -47,7 +45,7 @@ function requireScale(program, id, type) {
47
45
 
48
46
  function bounds(program) {
49
47
  const value = resolveGraphicBounds(program);
50
- const canvas = program.graphicSpec.objects.canvas;
48
+ const canvas = findCanvasGraphic(program);
51
49
  if (
52
50
  value === undefined ||
53
51
  ![value.x, value.y, value.width, value.height].every(Number.isFinite) ||
@@ -171,7 +169,7 @@ export const createSizeLegend = action(
171
169
  },
172
170
  function (args = {}) {
173
171
  validateKeys(args, [...SIZE_OPTIONS, "inheritAppearance"], "createSizeLegend");
174
- const layer = resolvePoint(this, args.target);
172
+ const layer = resolveSizeLegendPoint(this, args.target);
175
173
  const encoding = layer.encoding?.size;
176
174
  if (encoding?.scale === undefined) {
177
175
  throw new Error(`Point mark "${layer.id}" requires a size encoding.`);