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
@@ -9,6 +9,44 @@ function replaceAlternateBinding(program, target, channel, previous, usesField)
9
9
  : program;
10
10
  }
11
11
 
12
+ function reconcileInheritedRulePosition(program, {
13
+ target,
14
+ channel,
15
+ layer,
16
+ hasField
17
+ }) {
18
+ const config = program.markConfigs[target] ?? {};
19
+ const inherited = config.inheritedPosition;
20
+ if (layer.mark.type !== "rule" || inherited === undefined) return program;
21
+
22
+ let channels = inherited.channels.filter(candidate => candidate !== channel);
23
+ let next = program;
24
+ if (
25
+ !hasField &&
26
+ ["x", "y"].includes(channel) &&
27
+ layer.encoding?.x2 === undefined &&
28
+ layer.encoding?.y2 === undefined
29
+ ) {
30
+ const opposite = channel === "x" ? "y" : "x";
31
+ if (channels.includes(opposite) && layer.encoding?.[opposite] !== undefined) {
32
+ next = next.editSemantic({
33
+ property: `layer[${target}].encoding.${opposite}`,
34
+ remove: true
35
+ });
36
+ channels = channels.filter(candidate => candidate !== opposite);
37
+ }
38
+ }
39
+
40
+ const { inheritedPosition, ...rest } = config;
41
+ void inheritedPosition;
42
+ return next._withMarkConfig(target, channels.length === 0
43
+ ? rest
44
+ : {
45
+ ...rest,
46
+ inheritedPosition: { ...inherited, channels }
47
+ });
48
+ }
49
+
12
50
  function applyBin(program, target, channel, previous, bin) {
13
51
  if (bin === undefined) return program;
14
52
  const [mode] = Object.keys(bin);
@@ -26,9 +64,26 @@ function applyBin(program, target, channel, previous, bin) {
26
64
  });
27
65
  }
28
66
 
67
+ function applyArcThetaWeight(program, target, channel, previous, aggregate, weight) {
68
+ if (channel !== "theta") return program;
69
+ if (aggregate !== "sum") {
70
+ return previous?.weight === undefined
71
+ ? program
72
+ : program.editSemantic({
73
+ property: `layer[${target}].encoding.theta.weight`,
74
+ remove: true
75
+ });
76
+ }
77
+ return program.editSemantic({
78
+ property: `layer[${target}].encoding.theta.weight`,
79
+ value: weight
80
+ });
81
+ }
82
+
29
83
  export function applyPositionSemantics(program, {
30
84
  target,
31
85
  channel,
86
+ layer,
32
87
  previous,
33
88
  field,
34
89
  datum,
@@ -36,10 +91,17 @@ export function applyPositionSemantics(program, {
36
91
  fieldType,
37
92
  bin,
38
93
  aggregate,
39
- stack
94
+ stack,
95
+ weight
40
96
  }) {
41
- let next = replaceAlternateBinding(
42
- program,
97
+ let next = reconcileInheritedRulePosition(program, {
98
+ target,
99
+ channel,
100
+ layer,
101
+ hasField
102
+ });
103
+ next = replaceAlternateBinding(
104
+ next,
43
105
  target,
44
106
  channel,
45
107
  previous,
@@ -62,5 +124,12 @@ export function applyPositionSemantics(program, {
62
124
  value
63
125
  });
64
126
  }
65
- return next;
127
+ return applyArcThetaWeight(
128
+ next,
129
+ target,
130
+ channel,
131
+ previous,
132
+ aggregate,
133
+ weight
134
+ );
66
135
  }
@@ -1,14 +1,14 @@
1
- import { action } from "../../core/action.js";
1
+ import { action } from "../../../core/action.js";
2
2
  import {
3
3
  getPositionEncodingMaterializationSteps
4
- } from "../../materialization/marks.js";
5
- import { resolvePositionEncoding } from "./position/resolve.js";
6
- import { findLayer } from "../../selectors/layers.js";
7
- import { applyPositionSemantics } from "./position/apply.js";
4
+ } from "../../../materialization/marks/index.js";
5
+ import { resolvePositionEncoding } from "./resolve.js";
6
+ import { findLayer } from "../../../selectors/layers.js";
7
+ import { applyPositionSemantics } from "./apply.js";
8
8
  import {
9
9
  applyEncodingScale,
10
10
  rebindPositionGuides
11
- } from "./shared.js";
11
+ } from "../shared.js";
12
12
 
13
13
  function encodePosition(program, channel, args, operation) {
14
14
  const {
@@ -24,7 +24,8 @@ function encodePosition(program, channel, args, operation) {
24
24
  coordinate,
25
25
  bin,
26
26
  aggregate,
27
- stack
27
+ stack,
28
+ weight
28
29
  } = resolvePositionEncoding(program, channel, args, operation);
29
30
 
30
31
  let next = program
@@ -36,6 +37,7 @@ function encodePosition(program, channel, args, operation) {
36
37
  next = applyPositionSemantics(next, {
37
38
  target,
38
39
  channel,
40
+ layer,
39
41
  previous,
40
42
  field,
41
43
  datum,
@@ -43,7 +45,8 @@ function encodePosition(program, channel, args, operation) {
43
45
  fieldType,
44
46
  bin,
45
47
  aggregate,
46
- stack
48
+ stack,
49
+ weight
47
50
  });
48
51
 
49
52
  next = next.editSemantic({
@@ -71,6 +74,16 @@ function encodePosition(program, channel, args, operation) {
71
74
  }
72
75
  }
73
76
 
77
+ if (layer.mark.type === "rect") {
78
+ const updated = findLayer(next, target);
79
+ const pendingGradient = next.markConfigs[target]?.gradientPlot;
80
+ if (pendingGradient !== undefined && !pendingGradient.materialized) {
81
+ return updated.encoding?.x !== undefined && updated.encoding?.y !== undefined
82
+ ? next.materializeGradientPlot({ id: target })
83
+ : next;
84
+ }
85
+ }
86
+
74
87
  const updated = findLayer(next, target);
75
88
  for (const step of getPositionEncodingMaterializationSteps(
76
89
  next,
@@ -1,4 +1,5 @@
1
1
  import { validateAggregate } from "../../../../grammar/aggregate.js";
2
+ import { validateNonEmptyString } from "../../../../core/validation.js";
2
3
  import { emptyPositionPolicy } from "./common.js";
3
4
 
4
5
  export function resolveArcPositionPolicy({ channel, args, fieldType }) {
@@ -12,15 +13,37 @@ export function resolveArcPositionPolicy({ channel, args, fieldType }) {
12
13
  if (args.aggregate !== undefined) {
13
14
  throw new Error("Arc radius encoding does not support aggregate.");
14
15
  }
16
+ if (args.weight !== undefined) {
17
+ throw new Error("Arc radius encoding does not support weight.");
18
+ }
15
19
  return emptyPositionPolicy();
16
20
  }
17
21
  if (!["ordinal", "nominal"].includes(fieldType)) {
18
22
  throw new Error("Arc theta encoding requires an ordinal or nominal field.");
19
23
  }
20
- if (args.aggregate === undefined) return emptyPositionPolicy();
24
+ if (args.aggregate === undefined) {
25
+ if (args.weight !== undefined) {
26
+ throw new Error('Arc theta weight requires aggregate: "sum".');
27
+ }
28
+ return emptyPositionPolicy();
29
+ }
21
30
  const aggregate = validateAggregate(args.aggregate);
22
- if (aggregate !== "count") {
23
- throw new Error('Arc theta aggregate currently supports only "count".');
31
+ if (!["count", "sum"].includes(aggregate)) {
32
+ throw new Error('Arc theta aggregate supports only "count" or "sum".');
33
+ }
34
+ if (aggregate === "count") {
35
+ if (args.weight !== undefined) {
36
+ throw new Error('Arc theta weight requires aggregate: "sum".');
37
+ }
38
+ return { bin: undefined, aggregate, stack: undefined, weight: undefined };
39
+ }
40
+ if (args.weight === undefined) {
41
+ throw new Error('Arc theta aggregate "sum" requires weight.');
24
42
  }
25
- return { bin: undefined, aggregate, stack: undefined };
43
+ return {
44
+ bin: undefined,
45
+ aggregate,
46
+ stack: undefined,
47
+ weight: validateNonEmptyString(args.weight, "Arc theta weight")
48
+ };
26
49
  }
@@ -1,6 +1,37 @@
1
1
  import { validateStack, emptyPositionPolicy } from "./common.js";
2
2
 
3
- export function resolveAreaPositionPolicy({ dataset, channel, args, fieldType }) {
3
+ export function isCategoricalDensityPosition({
4
+ layer,
5
+ dataset,
6
+ channel,
7
+ fieldType,
8
+ field
9
+ }) {
10
+ const density = dataset.transform?.find(transform => transform.type === "density");
11
+ return layer.mark?.type === "area" &&
12
+ ["nominal", "ordinal"].includes(fieldType) &&
13
+ density?.placement?.type === "category" &&
14
+ density.placement.channel === channel &&
15
+ density.placement.categoryField === field;
16
+ }
17
+
18
+ export function resolveAreaPositionPolicy({ dataset, channel, args, fieldType, field }) {
19
+ const density = dataset.transform?.find(transform => transform.type === "density");
20
+ if (["nominal", "ordinal"].includes(fieldType)) {
21
+ if (
22
+ density?.placement?.type !== "category" ||
23
+ density.placement.channel !== channel ||
24
+ density.placement.categoryField !== field
25
+ ) {
26
+ throw new Error(
27
+ "Categorical area position requires a matching category density placement."
28
+ );
29
+ }
30
+ if (args.aggregate !== undefined || args.bin !== undefined || args.stack !== undefined) {
31
+ throw new Error("Categorical density position does not support aggregate, bin, or stack.");
32
+ }
33
+ return emptyPositionPolicy();
34
+ }
4
35
  if (!["quantitative", "temporal"].includes(fieldType)) {
5
36
  throw new Error(
6
37
  "Area position encoding requires quantitative or temporal fields."
@@ -12,7 +43,7 @@ export function resolveAreaPositionPolicy({ dataset, channel, args, fieldType })
12
43
  if (args.stack === undefined) return emptyPositionPolicy();
13
44
  if (
14
45
  channel !== "y" ||
15
- !dataset.transform?.some(transform => transform.type === "density")
46
+ density === undefined
16
47
  ) {
17
48
  throw new Error("Area stack currently requires a density y encoding.");
18
49
  }
@@ -1,6 +1,9 @@
1
1
  import { validatePositionFieldCompatibility } from
2
2
  "../../../../grammar/positionCompatibility.js";
3
- import { resolveAreaPositionPolicy } from "./area.js";
3
+ import {
4
+ isCategoricalDensityPosition,
5
+ resolveAreaPositionPolicy
6
+ } from "./area.js";
4
7
  import { resolveArcPositionPolicy } from "./arc.js";
5
8
  import { resolveBarPositionPolicy } from "./bar.js";
6
9
  import { resolveLinePositionPolicy } from "./line.js";
@@ -20,7 +23,9 @@ const POSITION_POLICIES = Object.freeze({
20
23
 
21
24
  export function resolveMarkPositionPolicy(context) {
22
25
  const { layer, channel, fieldType } = context;
23
- validatePositionFieldCompatibility(layer.mark.type, channel, fieldType);
26
+ if (!isCategoricalDensityPosition(context)) {
27
+ validatePositionFieldCompatibility(layer.mark.type, channel, fieldType);
28
+ }
24
29
  const policy = POSITION_POLICIES[layer.mark.type];
25
30
  if (policy === undefined) {
26
31
  throw new Error(`Unsupported position policy for mark "${layer.mark.type}".`);
@@ -44,9 +44,14 @@ export function resolveLinePositionPolicy({
44
44
  layer.encoding?.y?.aggregate === undefined &&
45
45
  ["quantitative", "temporal"].includes(layer.encoding?.y?.fieldType) &&
46
46
  fieldType === "quantitative";
47
+ const pendingQuantitativePair =
48
+ layer.encoding?.y === undefined && fieldType === "quantitative";
47
49
  if (
48
50
  fieldType !== "temporal" &&
49
- !((regression || interval || directPair) && fieldType === "quantitative")
51
+ !(
52
+ (regression || interval || directPair || pendingQuantitativePair) &&
53
+ fieldType === "quantitative"
54
+ )
50
55
  ) {
51
56
  throw new Error(
52
57
  "Line x encoding requires a temporal field or a compatible derived quantitative field."
@@ -60,10 +65,14 @@ export function resolveLinePositionPolicy({
60
65
  }
61
66
 
62
67
  rejectSharedOptions(args, "y");
68
+ const quantitativePair =
69
+ fieldType === "quantitative" &&
70
+ layer.encoding?.x?.fieldType === "quantitative";
63
71
  const prospectiveDirect =
64
72
  args.aggregate === undefined &&
65
73
  (fieldType === "temporal" ||
66
- (fieldType === "quantitative" && layer.encoding?.x === undefined));
74
+ (fieldType === "quantitative" &&
75
+ (layer.encoding?.x === undefined || quantitativePair)));
67
76
  if (interval || prospectiveDirect) {
68
77
  if (!["quantitative", "temporal"].includes(fieldType)) {
69
78
  throw new Error(
@@ -85,6 +94,13 @@ export function resolveLinePositionPolicy({
85
94
  return emptyPositionPolicy();
86
95
  }
87
96
 
97
+ if (
98
+ layer.encoding?.x !== undefined &&
99
+ layer.encoding.x.fieldType !== "temporal"
100
+ ) {
101
+ throw new Error("Aggregate line y encoding requires a temporal x encoding.");
102
+ }
103
+
88
104
  const aggregate = validateAggregate(args.aggregate);
89
105
  validateAggregateFieldType(aggregate, fieldType);
90
106
  return { bin: undefined, aggregate, stack: undefined };
@@ -7,7 +7,7 @@ import {
7
7
  readTemporalField,
8
8
  validateFieldType,
9
9
  validatePositionChannel
10
- } from "../../../grammar/scales.js";
10
+ } from "../../../grammar/scales/index.js";
11
11
  import { resolvePositionScaleDefinition } from "../../scales/definitions.js";
12
12
  import { findCoordinate } from "../../../selectors/coordinates.js";
13
13
  import {
@@ -21,6 +21,7 @@ import {
21
21
  validateAggregateFieldValues,
22
22
  } from "../../../grammar/aggregate.js";
23
23
  import { normalizeRuleDatum } from "../../../grammar/rules.js";
24
+ import { readArcThetaWeights } from "../../../grammar/arcs.js";
24
25
  import { resolveMarkPositionPolicy } from "./policies/index.js";
25
26
  import {
26
27
  getPositionChannelDefinition,
@@ -29,7 +30,7 @@ import {
29
30
 
30
31
  const POSITION_ENCODING_OPTIONS = Object.freeze([
31
32
  "field", "datum", "target", "fieldType", "scale", "coordinate",
32
- "aggregate", "bin", "stack"
33
+ "aggregate", "bin", "stack", "weight"
33
34
  ]);
34
35
 
35
36
  function validateCoordinateFamily(layer, channel, operation) {
@@ -82,6 +83,12 @@ export function resolvePositionEncoding(program, channel, args, operation) {
82
83
  args.target,
83
84
  getPositionChannelDefinition(channel).markTypes
84
85
  );
86
+ if (
87
+ Object.hasOwn(args, "weight") &&
88
+ !(layer.mark.type === "arc" && channel === "theta")
89
+ ) {
90
+ throw new Error(`${operation} weight is supported only for arc theta encoding.`);
91
+ }
85
92
  validateCoordinateFamily(layer, channel, operation);
86
93
  const hasField = Object.hasOwn(args, "field");
87
94
  const hasDatum = Object.hasOwn(args, "datum");
@@ -97,7 +104,8 @@ export function resolvePositionEncoding(program, channel, args, operation) {
97
104
  }
98
105
  const previous = layer.encoding?.[channel];
99
106
  const requestedFieldType = args.fieldType ?? previous?.fieldType ?? (
100
- layer.mark.type === "arc" && channel === "theta" && args.aggregate === "count"
107
+ layer.mark.type === "arc" && channel === "theta" &&
108
+ ["count", "sum"].includes(args.aggregate)
101
109
  ? "nominal"
102
110
  : "quantitative"
103
111
  );
@@ -117,6 +125,14 @@ export function resolvePositionEncoding(program, channel, args, operation) {
117
125
  effectiveArgs[property] = previous[property];
118
126
  }
119
127
  }
128
+ if (
129
+ !Object.hasOwn(effectiveArgs, "weight") &&
130
+ effectiveArgs.aggregate === "sum" &&
131
+ previous !== undefined &&
132
+ Object.hasOwn(previous, "weight")
133
+ ) {
134
+ effectiveArgs.weight = previous.weight;
135
+ }
120
136
  const policy = resolveMarkPositionPolicy({
121
137
  program,
122
138
  layer,
@@ -179,6 +195,12 @@ export function resolvePositionEncoding(program, channel, args, operation) {
179
195
  } else if (aggregateOutput) {
180
196
  validateAggregateFieldType(policy.aggregate, fieldType);
181
197
  validateAggregateFieldValues(dataset.values, field, fieldType);
198
+ } else if (
199
+ layer.mark.type === "arc" &&
200
+ channel === "theta" &&
201
+ policy.aggregate === "sum"
202
+ ) {
203
+ readArcThetaWeights(dataset.values, policy.weight, layer.id);
182
204
  } else if (program.markConfigs[target]?.boxPlot !== undefined) {
183
205
  for (const [index, row] of dataset.values.entries()) {
184
206
  const value = row[field];
@@ -7,12 +7,12 @@ import {
7
7
  readScaleField,
8
8
  readTemporalField,
9
9
  validateSemanticFieldType
10
- } from "../../grammar/scales.js";
10
+ } from "../../grammar/scales/index.js";
11
11
  import { normalizeRuleDatum } from "../../grammar/rules.js";
12
12
  import {
13
13
  canMaterializeArea,
14
14
  canMaterializeLine
15
- } from "../../materialization/marks.js";
15
+ } from "../../materialization/marks/index.js";
16
16
  import { findLayer } from "../../selectors/layers.js";
17
17
  import {
18
18
  resolveTarget,
@@ -157,7 +157,9 @@ function encodeSecondaryPosition(program, channel, args, operation, types) {
157
157
  ? next.rematerializeBarMark({ id: target })
158
158
  : layer.mark.type === "rect"
159
159
  ? next.rematerializeRectMark({ id: target })
160
- : next.rematerializeAreaMark({ id: target });
160
+ : canMaterializeArea(next, findLayer(next, target))
161
+ ? next.rematerializeAreaMark({ id: target })
162
+ : next;
161
163
  }
162
164
 
163
165
  const encodeX2 = action(
@@ -3,9 +3,22 @@ import {
3
3
  validateRuleStroke,
4
4
  validateRuleStrokeWidth
5
5
  } from "../../grammar/ruleAppearance.js";
6
- import { resolveTarget, validateOptions } from "./shared.js";
6
+ import { readQuantitativeField } from "../../grammar/scales/index.js";
7
+ import { resolveStrokeWidthScaleDefinition } from "../scales/definitions.js";
8
+ import {
9
+ applyEncodingScale,
10
+ resolveReassignmentScaleOptions,
11
+ resolveTarget,
12
+ validateOptions
13
+ } from "./shared.js";
14
+ import { applyMaterializationPlan } from "../../materialization/dependencies.js";
15
+ import { planEncodingRematerialization } from "../../materialization/encodings.js";
16
+ import { findLayer } from "../../selectors/layers.js";
7
17
 
8
- const OPTIONS = Object.freeze(["target", "value"]);
18
+ const STROKE_OPTIONS = Object.freeze(["target", "value"]);
19
+ const WIDTH_OPTIONS = Object.freeze([
20
+ "target", "value", "field", "fieldType", "scale"
21
+ ]);
9
22
 
10
23
  const encodeStroke = action(
11
24
  {
@@ -13,7 +26,7 @@ const encodeStroke = action(
13
26
  description: "Set a constant graphical stroke on a rule mark."
14
27
  },
15
28
  function (args = {}) {
16
- validateOptions(args, OPTIONS, "encodeStroke");
29
+ validateOptions(args, STROKE_OPTIONS, "encodeStroke");
17
30
  const { id } = resolveTarget(this, args.target, ["rule"], "rule mark");
18
31
  return this
19
32
  ._withMarkConfig(id, {
@@ -30,14 +43,79 @@ const encodeStrokeWidth = action(
30
43
  description: "Set a constant graphical stroke width on a rule mark."
31
44
  },
32
45
  function (args = {}) {
33
- validateOptions(args, OPTIONS, "encodeStrokeWidth");
34
- const { id } = resolveTarget(this, args.target, ["rule"], "rule mark");
35
- return this
36
- ._withMarkConfig(id, {
37
- ...this.markConfigs[id],
38
- strokeWidth: validateRuleStrokeWidth(args.value)
46
+ validateOptions(args, WIDTH_OPTIONS, "encodeStrokeWidth");
47
+ const hasValue = Object.hasOwn(args, "value");
48
+ const hasField = Object.hasOwn(args, "field");
49
+ if (hasValue === hasField) {
50
+ throw new Error("encodeStrokeWidth requires exactly one of value or field.");
51
+ }
52
+ if (hasValue) {
53
+ const { id } = resolveTarget(this, args.target, ["rule"], "rule mark");
54
+ let next = this.guideConfigs.legend?.strokeWidth?.target === id
55
+ ? this.removeLegend({ target: id })
56
+ : this;
57
+ if (findLayer(next, id)?.encoding?.strokeWidth !== undefined) {
58
+ next = next.editSemantic({
59
+ property: `layer[${id}].encoding.strokeWidth`,
60
+ remove: true
61
+ });
62
+ }
63
+ return next
64
+ ._withMarkConfig(id, {
65
+ ...next.markConfigs[id],
66
+ strokeWidth: validateRuleStrokeWidth(args.value)
67
+ })
68
+ .rematerializeRuleMark({ id });
69
+ }
70
+ const { id, dataset, layer } = resolveTarget(
71
+ this,
72
+ args.target,
73
+ ["rule", "line"],
74
+ "rule or line mark"
75
+ );
76
+ const fieldType = args.fieldType ?? "quantitative";
77
+ if (fieldType !== "quantitative") {
78
+ throw new Error("encodeStrokeWidth requires a quantitative field.");
79
+ }
80
+ const values = readQuantitativeField(dataset.values, args.field);
81
+ if (values.some(value => value < 0)) {
82
+ throw new RangeError(
83
+ `encodeStrokeWidth field "${args.field}" cannot contain negative values.`
84
+ );
85
+ }
86
+ const previous = layer.encoding?.strokeWidth;
87
+ const requestedScale = resolveReassignmentScaleOptions(
88
+ previous,
89
+ args.scale ?? {}
90
+ );
91
+ const scale = resolveStrokeWidthScaleDefinition(this, requestedScale);
92
+ const { strokeWidth, ...config } = this.markConfigs[id] ?? {};
93
+ void strokeWidth;
94
+ let next = this
95
+ ._withMarkConfig(id, config)
96
+ .editSemantic({
97
+ property: `layer[${id}].encoding.strokeWidth.field`,
98
+ value: args.field
39
99
  })
40
- .rematerializeRuleMark({ id });
100
+ .editSemantic({
101
+ property: `layer[${id}].encoding.strokeWidth.fieldType`,
102
+ value: fieldType
103
+ })
104
+ .editSemantic({
105
+ property: `layer[${id}].encoding.strokeWidth.scale`,
106
+ value: scale.id
107
+ });
108
+ next = applyEncodingScale(next, scale, requestedScale, {
109
+ reassignment: previous?.scale === scale.id
110
+ });
111
+ return applyMaterializationPlan(
112
+ next,
113
+ planEncodingRematerialization(next, {
114
+ target: id,
115
+ channel: "strokeWidth",
116
+ scale: scale.id
117
+ })
118
+ );
41
119
  }
42
120
  );
43
121
 
@@ -3,7 +3,7 @@ import {
3
3
  normalizeStrokeDashPattern,
4
4
  readNominalField,
5
5
  validateNominalFieldType
6
- } from "../../grammar/scales.js";
6
+ } from "../../grammar/scales/index.js";
7
7
  import { resolveStrokeDashScaleDefinition } from "../scales/definitions.js";
8
8
  import { applyMaterializationPlan } from "../../materialization/dependencies.js";
9
9
  import { planEncodingRematerialization } from "../../materialization/encodings.js";
@@ -1,6 +1,6 @@
1
1
  import { action } from "../../core/action.js";
2
2
  import { formatTextValue, validateTextFormat } from "../../grammar/text.js";
3
- import { canMaterializeText } from "../../materialization/marks.js";
3
+ import { canMaterializeText } from "../../materialization/marks/index.js";
4
4
  import { findLayer } from "../../selectors/layers.js";
5
5
  import { resolveTarget, validateOptions } from "./shared.js";
6
6
 
@@ -6,7 +6,7 @@ import {
6
6
  validateUnitInterval
7
7
  } from "../../core/validation.js";
8
8
  import { validateCurveInterpolation } from "../../grammar/curveCommands.js";
9
- import { normalizeStrokeDashPattern } from "../../grammar/scales.js";
9
+ import { normalizeStrokeDashPattern } from "../../grammar/scales/index.js";
10
10
 
11
11
  export const ERROR_BAND_BOUNDARY_OPTIONS = Object.freeze([
12
12
  "stroke", "strokeWidth", "strokeDash", "opacity", "curve"
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  normalizeStrokeDashPattern,
7
7
  validateOpacityValue
8
- } from "../../grammar/scales.js";
8
+ } from "../../grammar/scales/index.js";
9
9
  import {
10
10
  validateRuleStroke,
11
11
  validateRuleStrokeWidth
@@ -6,7 +6,7 @@ import {
6
6
  validateOptionObject,
7
7
  validatePositiveFinite
8
8
  } from "../../core/validation.js";
9
- import { resolveFacetDefinition } from "../../grammar/facets.js";
9
+ import { resolveFacetDefinition } from "../../grammar/facets/index.js";
10
10
  import { normalizeFacetScalePolicies } from
11
11
  "../../grammar/facets/scales.js";
12
12
  import { resolveFacetLayout } from "../../layout/facets.js";
@@ -14,7 +14,7 @@ import { compositionChildDescriptor } from
14
14
  "../../materialization/composition.js";
15
15
  import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from "../../theme/defaults.js";
16
16
  import { deriveFacetChildren } from "./derive.js";
17
- import { rebindLayerData, replayDerivedData } from "./replay.js";
17
+ import { replayDerivedData } from "./replay.js";
18
18
  import { composeFacetGuides } from "./guides.js";
19
19
 
20
20
  const FACET_OPTIONS = Object.freeze([
@@ -155,7 +155,6 @@ export const editFacetHeaders = action(
155
155
 
156
156
  export function registerFacetActions(ProgramClass) {
157
157
  ProgramClass.prototype.replayDerivedData = replayDerivedData;
158
- ProgramClass.prototype.rebindLayerData = rebindLayerData;
159
158
  ProgramClass.prototype.composeFacetGuides = composeFacetGuides;
160
159
  ProgramClass.prototype.facet = facet;
161
160
  ProgramClass.prototype.editFacetHeaders = editFacetHeaders;