ggaction 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/CHANGELOG.md +55 -2
  2. package/README.md +95 -72
  3. package/package.json +15 -5
  4. package/src/actions/boxPlots/components.js +3 -3
  5. package/src/actions/boxPlots/edit.js +277 -0
  6. package/src/actions/boxPlots/index.js +2 -0
  7. package/src/actions/boxPlots/materialize.js +12 -1
  8. package/src/actions/boxPlots/options.js +25 -14
  9. package/src/actions/canvas/actions.js +21 -17
  10. package/src/actions/composition/actions.js +247 -0
  11. package/src/actions/composition/index.js +1 -0
  12. package/src/actions/coordinates/actions.js +2 -5
  13. package/src/actions/data/density.js +7 -1
  14. package/src/actions/data/filter.js +3 -1
  15. package/src/actions/data/regression.js +1 -1
  16. package/src/actions/encodings/appearance.js +12 -1
  17. package/src/actions/encodings/barWidth.js +5 -3
  18. package/src/actions/encodings/color/continuous.js +137 -0
  19. package/src/actions/encodings/color/index.js +151 -0
  20. package/src/actions/encodings/color/layout.js +91 -0
  21. package/src/actions/encodings/color/policy.js +88 -0
  22. package/src/actions/encodings/index.js +4 -2
  23. package/src/actions/encodings/offset.js +91 -73
  24. package/src/actions/encodings/position/apply.js +66 -0
  25. package/src/actions/encodings/position/index.js +130 -0
  26. package/src/actions/encodings/position/policies/arc.js +26 -0
  27. package/src/actions/encodings/position/policies/bar.js +9 -5
  28. package/src/actions/encodings/position/policies/index.js +5 -1
  29. package/src/actions/encodings/position/policies/line.js +20 -3
  30. package/src/actions/encodings/position/resolve.js +58 -9
  31. package/src/actions/encodings/ranged.js +30 -8
  32. package/src/actions/encodings/shared.js +17 -21
  33. package/src/actions/encodings/strokeDash.js +1 -1
  34. package/src/actions/encodings/text.js +73 -0
  35. package/src/actions/errorBands/create.js +37 -29
  36. package/src/actions/errorBands/edit.js +206 -0
  37. package/src/actions/errorBands/index.js +9 -0
  38. package/src/actions/errorBands/options.js +39 -0
  39. package/src/actions/errorBars/create.js +50 -43
  40. package/src/actions/errorBars/edit.js +130 -0
  41. package/src/actions/errorBars/index.js +3 -0
  42. package/src/actions/errorBars/options.js +52 -0
  43. package/src/actions/facets/actions.js +162 -0
  44. package/src/actions/facets/derive.js +200 -0
  45. package/src/actions/facets/guides.js +16 -0
  46. package/src/actions/facets/index.js +1 -0
  47. package/src/actions/facets/replay.js +69 -0
  48. package/src/actions/guides/applicability.js +113 -0
  49. package/src/actions/guides/axes/axes.js +70 -16
  50. package/src/actions/guides/axes/edit.js +126 -0
  51. package/src/actions/guides/axes/index.js +12 -0
  52. package/src/actions/guides/axes/labels.js +22 -19
  53. package/src/actions/guides/axes/remove.js +60 -0
  54. package/src/actions/guides/axes/tickGroups.js +7 -5
  55. package/src/actions/guides/axes/ticks.js +10 -5
  56. package/src/actions/guides/axes/titles.js +8 -6
  57. package/src/actions/guides/grids/grid.js +144 -15
  58. package/src/actions/guides/grids/resolve.js +6 -14
  59. package/src/actions/guides/guides.js +17 -75
  60. package/src/actions/guides/index.js +6 -0
  61. package/src/actions/guides/legends/categorical/actions.js +48 -9
  62. package/src/actions/guides/legends/categorical/components.js +16 -16
  63. package/src/actions/guides/legends/categorical/index.js +14 -14
  64. package/src/actions/guides/legends/categorical/layout.js +7 -3
  65. package/src/actions/guides/legends/categorical/options.js +2 -5
  66. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  67. package/src/actions/guides/legends/categorical/symbols.js +14 -14
  68. package/src/actions/guides/legends/continuous/common.js +7 -7
  69. package/src/actions/guides/legends/continuous/interval.js +6 -6
  70. package/src/actions/guides/legends/continuous/opacity.js +1 -1
  71. package/src/actions/guides/legends/edit.js +4 -26
  72. package/src/actions/guides/legends/focused.js +63 -0
  73. package/src/actions/guides/legends/index.js +4 -0
  74. package/src/actions/guides/legends/remove.js +46 -0
  75. package/src/actions/guides/legends/size.js +13 -15
  76. package/src/actions/guides/legends/target.js +25 -0
  77. package/src/actions/guides/polar/axes/facade.js +219 -0
  78. package/src/actions/guides/polar/axes/index.js +55 -0
  79. package/src/actions/guides/polar/axes/labels.js +188 -0
  80. package/src/actions/guides/polar/axes/lines.js +113 -0
  81. package/src/actions/guides/polar/axes/shared.js +112 -0
  82. package/src/actions/guides/polar/axes/ticks.js +164 -0
  83. package/src/actions/guides/polar/axes/titles.js +165 -0
  84. package/src/actions/guides/polar/grids.js +252 -0
  85. package/src/actions/guides/polar/index.js +2 -0
  86. package/src/actions/guides/polar/resolve.js +269 -0
  87. package/src/actions/guides/tickValues.js +1 -1
  88. package/src/actions/index.js +4 -0
  89. package/src/actions/intervals/resolve.js +1 -1
  90. package/src/actions/marks/arc/actions.js +239 -0
  91. package/src/actions/marks/arc/index.js +1 -0
  92. package/src/actions/marks/{area.js → area/actions.js} +51 -38
  93. package/src/actions/marks/area/index.js +4 -0
  94. package/src/actions/marks/bar/create.js +26 -5
  95. package/src/actions/marks/bar/edit.js +1 -1
  96. package/src/actions/marks/bar/materialize.js +25 -31
  97. package/src/actions/marks/index.js +12 -4
  98. package/src/actions/marks/lifecycle.js +27 -0
  99. package/src/actions/marks/line/actions.js +373 -0
  100. package/src/actions/marks/line/index.js +1 -0
  101. package/src/actions/marks/{point.js → point/actions.js} +67 -108
  102. package/src/actions/marks/point/index.js +1 -0
  103. package/src/actions/marks/rect/actions.js +165 -0
  104. package/src/actions/marks/rect/index.js +1 -0
  105. package/src/actions/marks/remove.js +214 -0
  106. package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
  107. package/src/actions/marks/rule/index.js +1 -0
  108. package/src/actions/marks/shared.js +156 -25
  109. package/src/actions/marks/text/actions.js +192 -0
  110. package/src/actions/marks/text/index.js +1 -0
  111. package/src/actions/primitives/createGraphics.js +13 -6
  112. package/src/actions/primitives/editGraphics.js +10 -3
  113. package/src/actions/primitives/semantic.js +26 -3
  114. package/src/actions/primitives/semanticValue.js +26 -4
  115. package/src/actions/regression/components.js +2 -2
  116. package/src/actions/regression/create.js +19 -2
  117. package/src/actions/regression/edit.js +247 -0
  118. package/src/actions/regression/index.js +3 -0
  119. package/src/actions/regression/resolve.js +3 -1
  120. package/src/actions/scales/consumers/common.js +91 -0
  121. package/src/actions/scales/consumers/families.js +47 -0
  122. package/src/actions/scales/consumers/index.js +41 -0
  123. package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +14 -127
  124. package/src/actions/scales/create.js +1 -1
  125. package/src/actions/scales/definitions.js +15 -6
  126. package/src/actions/scales/edit.js +45 -22
  127. package/src/actions/scales/materialize.js +58 -409
  128. package/src/actions/selection/actions.js +38 -24
  129. package/src/actions/selection/index.js +2 -0
  130. package/src/actions/titles/actions.js +63 -2
  131. package/src/actions/titles/index.js +2 -0
  132. package/src/actions/titles/resolve.js +12 -17
  133. package/src/composition.js +10 -0
  134. package/src/core/ChartProgram.js +49 -65
  135. package/src/core/action.js +10 -0
  136. package/src/core/compositionState.js +169 -0
  137. package/src/core/materializationState.js +65 -0
  138. package/src/core/programState.js +28 -0
  139. package/src/core/validation.js +16 -0
  140. package/src/core/vocabulary.js +154 -4
  141. package/src/grammar/arcs.js +154 -0
  142. package/src/grammar/areaSeries.js +1 -1
  143. package/src/grammar/bars/aggregate.js +3 -3
  144. package/src/grammar/bars/geometry.js +3 -3
  145. package/src/grammar/bars/policy.js +7 -1
  146. package/src/grammar/coordinates.js +3 -7
  147. package/src/grammar/density.js +21 -2
  148. package/src/grammar/facets/dependencies.js +185 -0
  149. package/src/grammar/facets/guides.js +257 -0
  150. package/src/grammar/facets/index.js +122 -0
  151. package/src/grammar/facets/scales.js +254 -0
  152. package/src/grammar/interval.js +2 -96
  153. package/src/grammar/lineSeries.js +112 -4
  154. package/src/grammar/palettes.js +5 -7
  155. package/src/grammar/polar.js +102 -0
  156. package/src/grammar/polarGuides.js +243 -0
  157. package/src/grammar/polarLineCommands.js +43 -0
  158. package/src/grammar/polarPaths.js +161 -0
  159. package/src/grammar/positionCompatibility.js +18 -2
  160. package/src/grammar/rects.js +26 -0
  161. package/src/grammar/regression/derive.js +97 -0
  162. package/src/grammar/regression/index.js +22 -0
  163. package/src/grammar/regression/models.js +282 -0
  164. package/src/grammar/regression/parameters.js +106 -0
  165. package/src/grammar/rules.js +1 -1
  166. package/src/grammar/scales/color.js +4 -2
  167. package/src/grammar/scales/continuous.js +6 -1
  168. package/src/grammar/scales/fields.js +7 -0
  169. package/src/grammar/scales/ordinal.js +5 -2
  170. package/src/grammar/scales/policies.js +1 -1
  171. package/src/grammar/schemas/graphic.js +1 -1
  172. package/src/grammar/schemas/graphicBounds.js +64 -9
  173. package/src/grammar/schemas/graphicTree.js +44 -10
  174. package/src/grammar/schemas/semanticPath.js +31 -9
  175. package/src/grammar/seriesLayout.js +13 -2
  176. package/src/grammar/statistics/studentT.js +98 -0
  177. package/src/grammar/text.js +71 -0
  178. package/src/grammar/ticks.js +50 -13
  179. package/src/grammar/transforms.js +1 -1
  180. package/src/index.js +1 -0
  181. package/src/layout/composition.js +310 -0
  182. package/src/layout/facets.js +131 -0
  183. package/src/layout/title.js +6 -0
  184. package/src/materialization/bars/aggregate.js +6 -3
  185. package/src/materialization/bars/grouped.js +75 -37
  186. package/src/materialization/bars/histogram.js +1 -1
  187. package/src/materialization/bars/ranged.js +1 -1
  188. package/src/materialization/composition.js +152 -0
  189. package/src/materialization/compositionSnapshot.js +114 -0
  190. package/src/materialization/density.js +1 -1
  191. package/src/materialization/dependencies.js +47 -73
  192. package/src/materialization/encodings.js +14 -28
  193. package/src/materialization/facetGuides/index.js +2 -0
  194. package/src/materialization/facetGuides/legacyCategorical.js +125 -0
  195. package/src/materialization/facetGuides/placement.js +126 -0
  196. package/src/materialization/facetGuides/preparation.js +188 -0
  197. package/src/materialization/facets.js +214 -0
  198. package/src/materialization/graphicHierarchy.js +15 -5
  199. package/src/materialization/guides/resources.js +46 -0
  200. package/src/materialization/marks/capabilities.js +141 -0
  201. package/src/materialization/marks/index.js +105 -0
  202. package/src/materialization/marks/policies.js +87 -0
  203. package/src/materialization/planner.js +105 -12
  204. package/src/materialization/rect.js +138 -0
  205. package/src/materialization/rectConfig.js +38 -0
  206. package/src/materialization/rowEncoding.js +33 -0
  207. package/src/materialization/scaleGuideDependencies.js +94 -0
  208. package/src/materialization/scales/map.js +40 -0
  209. package/src/materialization/scales/policies/arc.js +37 -0
  210. package/src/materialization/scales/policies/bar.js +132 -0
  211. package/src/materialization/scales/policies/series.js +76 -0
  212. package/src/materialization/scales/resolve.js +326 -0
  213. package/src/materialization/selection/items/arc.js +29 -0
  214. package/src/materialization/selection/items/bar.js +255 -0
  215. package/src/materialization/selection/items/common.js +131 -0
  216. package/src/materialization/selection/items/index.js +6 -0
  217. package/src/materialization/selection/items/path.js +59 -0
  218. package/src/materialization/selection/items/point.js +55 -0
  219. package/src/materialization/selection/items/rect.js +23 -0
  220. package/src/materialization/selection/items/rule.js +25 -0
  221. package/src/materialization/selection/policies/arc.js +10 -0
  222. package/src/materialization/selection/policies/area.js +1 -1
  223. package/src/materialization/selection/policies/bar.js +1 -1
  224. package/src/materialization/selection/policies/index.js +5 -1
  225. package/src/materialization/selection/policies/line.js +1 -1
  226. package/src/materialization/selection/policies/point.js +1 -1
  227. package/src/materialization/selection/policies/rect.js +10 -0
  228. package/src/materialization/selection/policies/rule.js +1 -1
  229. package/src/materialization/selection/styles.js +25 -17
  230. package/src/materialization/text.js +156 -0
  231. package/src/renderers/canvas/index.js +68 -22
  232. package/src/renderers/canvas/text.js +14 -1
  233. package/types/extension.d.ts +5 -1
  234. package/types/index.d.ts +56 -1
  235. package/types/program.d.ts +664 -27
  236. package/src/actions/encodings/color.js +0 -329
  237. package/src/actions/encodings/position.js +0 -176
  238. package/src/actions/marks/line.js +0 -248
  239. package/src/grammar/regression.js +0 -578
  240. package/src/grammar/scales.js +0 -1
  241. package/src/materialization/marks.js +0 -110
  242. package/src/materialization/selection/items.js +0 -503
@@ -0,0 +1,137 @@
1
+ import {
2
+ readQuantitativeField,
3
+ readScaleField,
4
+ readTemporalField
5
+ } from "../../../grammar/scales/index.js";
6
+ import {
7
+ BAR_GRAINS,
8
+ resolveBarChannels,
9
+ resolveBarGrain
10
+ } from "../../../grammar/bars/policy.js";
11
+ import {
12
+ validateAggregate,
13
+ validateAggregateFieldType,
14
+ validateAggregateFieldValues
15
+ } from "../../../grammar/aggregate.js";
16
+ import {
17
+ resolveQuantitativeColorScaleDefinition
18
+ } from "../../scales/definitions.js";
19
+ import {
20
+ resolveReassignmentScaleOptions,
21
+ resolveTarget
22
+ } from "../shared.js";
23
+ import { applyMaterializationPlan } from "../../../materialization/dependencies.js";
24
+ import {
25
+ planEncodingRematerialization
26
+ } from "../../../materialization/encodings.js";
27
+ import {
28
+ assertNoConstantColor,
29
+ resolveColorScaleOptions
30
+ } from "./policy.js";
31
+
32
+ export function encodeContinuousColor(program, args) {
33
+ if (!["quantitative", "temporal"].includes(args.fieldType)) {
34
+ throw new Error(`Unsupported color field type "${args.fieldType}".`);
35
+ }
36
+ if (args.layout !== undefined) {
37
+ throw new Error("Continuous color does not support layout.");
38
+ }
39
+ const { id: target, dataset, layer } = resolveTarget(
40
+ program,
41
+ args.target,
42
+ ["point", "bar", "rect"],
43
+ "continuous color mark"
44
+ );
45
+ assertNoConstantColor(program, layer);
46
+ const requestedScale = resolveReassignmentScaleOptions(
47
+ layer.encoding?.color,
48
+ resolveColorScaleOptions(args)
49
+ );
50
+ const scale = resolveQuantitativeColorScaleDefinition(
51
+ program,
52
+ args.fieldType,
53
+ requestedScale
54
+ );
55
+ if (Object.hasOwn(scale, "unknown") && layer.mark.type !== "point") {
56
+ throw new Error(
57
+ "Continuous color scale unknown currently requires a row-owned point mark."
58
+ );
59
+ }
60
+ if (
61
+ layer.mark.type === "bar" &&
62
+ ["nominal", "ordinal"].includes(layer.encoding?.color?.fieldType)
63
+ ) {
64
+ throw new Error(
65
+ "Continuous bar color cannot replace an existing nominal color layout."
66
+ );
67
+ }
68
+ let aggregate;
69
+ if (["point", "rect"].includes(layer.mark.type)) {
70
+ if (args.aggregate !== undefined) {
71
+ throw new Error(`${layer.mark.type} continuous color does not support aggregate.`);
72
+ }
73
+ } else {
74
+ if (args.fieldType !== "quantitative") {
75
+ throw new Error("Aggregate bar color currently requires a quantitative field.");
76
+ }
77
+ if (resolveBarGrain(layer) !== BAR_GRAINS.aggregate) {
78
+ throw new Error(
79
+ "Continuous bar color requires a complete categorical aggregate bar."
80
+ );
81
+ }
82
+ const channels = resolveBarChannels(layer);
83
+ const measure = layer.encoding?.[channels.measure];
84
+ aggregate = args.aggregate ?? (
85
+ measure?.field === args.field ? measure.aggregate : undefined
86
+ );
87
+ if (aggregate === undefined) {
88
+ throw new Error(
89
+ "Continuous bar color requires aggregate when its field differs from the measure field."
90
+ );
91
+ }
92
+ aggregate = validateAggregate(aggregate);
93
+ validateAggregateFieldType(aggregate, args.fieldType);
94
+ validateAggregateFieldValues(dataset.values, args.field, args.fieldType);
95
+ }
96
+ if (layer.mark.type === "rect") {
97
+ readScaleField(dataset.values, args.field, args.fieldType, {
98
+ allowUnknown: true
99
+ });
100
+ } else if (Object.hasOwn(scale, "unknown")) {
101
+ readScaleField(dataset.values, args.field, args.fieldType, {
102
+ allowUnknown: true
103
+ });
104
+ } else if (args.fieldType === "temporal") {
105
+ readTemporalField(dataset.values, args.field);
106
+ } else {
107
+ readQuantitativeField(dataset.values, args.field);
108
+ }
109
+ const next = program
110
+ .editSemantic({
111
+ property: `layer[${target}].encoding.color.field`,
112
+ value: args.field
113
+ })
114
+ .editSemantic({
115
+ property: `layer[${target}].encoding.color.fieldType`,
116
+ value: args.fieldType
117
+ })
118
+ .editSemantic({
119
+ property: `layer[${target}].encoding.color.scale`,
120
+ value: scale.id
121
+ });
122
+ const encoded = aggregate === undefined
123
+ ? next
124
+ : next.editSemantic({
125
+ property: `layer[${target}].encoding.color.aggregate`,
126
+ value: aggregate
127
+ });
128
+ const scaled = encoded.setQuantitativeColorScale(scale);
129
+ return applyMaterializationPlan(
130
+ scaled,
131
+ planEncodingRematerialization(scaled, {
132
+ target,
133
+ channel: "color",
134
+ scale: scale.id
135
+ })
136
+ );
137
+ }
@@ -0,0 +1,151 @@
1
+ import { action } from "../../../core/action.js";
2
+ import {
3
+ readNominalField,
4
+ readScaleField,
5
+ validateCategoricalFieldType
6
+ } from "../../../grammar/scales/index.js";
7
+ import {
8
+ resolveBarGrain
9
+ } from "../../../grammar/bars/policy.js";
10
+ import { resolveColorScaleDefinition } from "../../scales/definitions.js";
11
+ import {
12
+ applyEncodingScale,
13
+ resolveReassignmentScaleOptions,
14
+ resolveTarget,
15
+ validateLineSeriesCompatibility,
16
+ validateOptions
17
+ } from "../shared.js";
18
+ import { applyMaterializationPlan } from "../../../materialization/dependencies.js";
19
+ import {
20
+ planEncodingRematerialization
21
+ } from "../../../materialization/encodings.js";
22
+ import { encodeContinuousColor } from "./continuous.js";
23
+ import {
24
+ applyColorLayoutCompanion,
25
+ preSynchronizeGroupedOffset
26
+ } from "./layout.js";
27
+ import {
28
+ assertNoConstantColor,
29
+ COLOR_ENCODING_OPTIONS,
30
+ resolveColorLayout,
31
+ resolveColorScaleOptions
32
+ } from "./policy.js";
33
+
34
+ const encodeColor = action(
35
+ {
36
+ op: "encodeColor",
37
+ description: "Encode a field as graphical color."
38
+ },
39
+ function (args = {}) {
40
+ validateOptions(args, COLOR_ENCODING_OPTIONS, "encodeColor");
41
+ const requestedFieldType = args.fieldType ?? "nominal";
42
+ if (!["nominal", "ordinal"].includes(requestedFieldType)) {
43
+ return encodeContinuousColor(this, {
44
+ ...args,
45
+ fieldType: requestedFieldType
46
+ });
47
+ }
48
+ if (args.aggregate !== undefined) {
49
+ throw new Error("Categorical color does not support aggregate.");
50
+ }
51
+ const fieldType = validateCategoricalFieldType(requestedFieldType);
52
+ const { id: target, dataset, layer } = resolveTarget(
53
+ this,
54
+ args.target,
55
+ ["point", "line", "bar", "area", "arc", "rect"],
56
+ "color mark"
57
+ );
58
+ assertNoConstantColor(this, layer);
59
+ if (
60
+ layer.mark.type === "area" &&
61
+ (layer.encoding?.group?.field === undefined ||
62
+ layer.encoding.group.field !== args.field)
63
+ ) {
64
+ throw new Error(
65
+ "Area color encoding must match an existing group encoding."
66
+ );
67
+ }
68
+ validateLineSeriesCompatibility(layer, "color", args.field);
69
+
70
+ const barGrain = resolveBarGrain(layer);
71
+ if (layer.mark.type === "bar" && barGrain === undefined) {
72
+ throw new Error(
73
+ "Bar color encoding requires a complete histogram encoding or a complete ordinal aggregate encoding."
74
+ );
75
+ }
76
+ const layout = resolveColorLayout(layer, args.layout, barGrain);
77
+ const requestedScale = resolveReassignmentScaleOptions(
78
+ layer.encoding?.color,
79
+ resolveColorScaleOptions(args)
80
+ );
81
+ const scale = resolveColorScaleDefinition(this, requestedScale);
82
+ if (Object.hasOwn(scale, "unknown") && layer.mark.type !== "point") {
83
+ throw new Error(
84
+ "Categorical color scale unknown currently requires a row-owned point mark."
85
+ );
86
+ }
87
+ if (layer.mark.type === "rect") {
88
+ readScaleField(dataset.values, args.field, fieldType, {
89
+ allowUnknown: true
90
+ });
91
+ } else if (Object.hasOwn(scale, "unknown")) {
92
+ readScaleField(dataset.values, args.field, fieldType, {
93
+ allowUnknown: true
94
+ });
95
+ } else {
96
+ readNominalField(dataset.values, args.field);
97
+ }
98
+
99
+ let next = this
100
+ .editSemantic({
101
+ property: `layer[${target}].encoding.color.field`,
102
+ value: args.field
103
+ })
104
+ .editSemantic({
105
+ property: `layer[${target}].encoding.color.fieldType`,
106
+ value: fieldType
107
+ })
108
+ .editSemantic({
109
+ property: `layer[${target}].encoding.color.scale`,
110
+ value: scale.id
111
+ });
112
+ if (layout !== undefined) {
113
+ next = next.editSemantic({
114
+ property: `layer[${target}].encoding.color.layout`,
115
+ value: layout
116
+ });
117
+ }
118
+ next = preSynchronizeGroupedOffset(next, {
119
+ target,
120
+ layer,
121
+ layout,
122
+ field: args.field,
123
+ fieldType,
124
+ scale,
125
+ requestedScale
126
+ });
127
+ next = applyEncodingScale(next, scale, requestedScale, {
128
+ reassignment: layer.encoding?.color?.scale === scale.id
129
+ });
130
+ next = applyColorLayoutCompanion(next, {
131
+ target,
132
+ layer,
133
+ layout,
134
+ scale,
135
+ field: args.field
136
+ });
137
+
138
+ return applyMaterializationPlan(
139
+ next,
140
+ planEncodingRematerialization(next, {
141
+ target,
142
+ channel: "color",
143
+ scale: scale.id
144
+ })
145
+ );
146
+ }
147
+ );
148
+
149
+ export function registerColorEncodingAction(ProgramClass) {
150
+ ProgramClass.prototype.encodeColor = encodeColor;
151
+ }
@@ -0,0 +1,91 @@
1
+ import {
2
+ BAR_GRAINS,
3
+ resolveBarChannels,
4
+ resolveBarGrain,
5
+ resolveBarOffsetChannel
6
+ } from "../../../grammar/bars/policy.js";
7
+ import { resolveOffsetScaleDefinition } from "../../scales/definitions.js";
8
+ import { isRangedArea } from "./policy.js";
9
+
10
+ export function applyColorLayoutCompanion(
11
+ program,
12
+ { target, layer, layout, scale, field }
13
+ ) {
14
+ if (layout === undefined || layer.mark.type === "arc") return program;
15
+ if (
16
+ layer.mark.type === "bar" &&
17
+ resolveBarGrain(layer) === BAR_GRAINS.ranged
18
+ ) {
19
+ return program;
20
+ }
21
+ if (isRangedArea(layer)) return program;
22
+ const channels = layer.mark.type === "bar"
23
+ ? resolveBarChannels(layer)
24
+ : undefined;
25
+ const measureChannel = channels?.measure ?? "y";
26
+ const measure = layer.encoding?.[measureChannel];
27
+ if (measure?.field === undefined || measure.scale === undefined) {
28
+ throw new Error(`Color layout on mark "${target}" requires a measure encoding.`);
29
+ }
30
+ const stack = layout === "fill"
31
+ ? "normalize"
32
+ : layout === "overlay" || layout === "group"
33
+ ? null
34
+ : "zero";
35
+ let next = program;
36
+ if (layout === "group") {
37
+ const offsetChannel = resolveBarOffsetChannel(layer);
38
+ const method = offsetChannel === "xOffset" ? "encodeXOffset" : "encodeYOffset";
39
+ next = next[method]({
40
+ field,
41
+ target,
42
+ scale: {
43
+ ...(layer.encoding?.[offsetChannel]?.scale === undefined
44
+ ? {}
45
+ : { id: layer.encoding[offsetChannel].scale }),
46
+ domain: scale.domain
47
+ }
48
+ });
49
+ }
50
+ return next[measureChannel === "x" ? "encodeX" : "encodeY"]({
51
+ target,
52
+ field: measure.field,
53
+ fieldType: measure.fieldType,
54
+ ...(measure.aggregate === undefined ? {} : { aggregate: measure.aggregate }),
55
+ stack
56
+ });
57
+ }
58
+
59
+ export function preSynchronizeGroupedOffset(
60
+ program,
61
+ { target, layer, layout, field, fieldType, scale, requestedScale }
62
+ ) {
63
+ if (
64
+ layer.mark.type !== "bar" ||
65
+ layout !== "group" ||
66
+ layer.encoding?.color?.scale !== scale.id ||
67
+ !Object.keys(requestedScale).some(key => key !== "id")
68
+ ) {
69
+ return program;
70
+ }
71
+ const offsetChannel = resolveBarOffsetChannel(layer);
72
+ const offset = layer.encoding?.[offsetChannel];
73
+ if (offset?.scale === undefined) return program;
74
+ const offsetScale = resolveOffsetScaleDefinition(program, {
75
+ id: offset.scale,
76
+ domain: scale.domain
77
+ }, offsetChannel);
78
+ return program
79
+ .editSemantic({
80
+ property: `layer[${target}].encoding.${offsetChannel}.field`,
81
+ value: field
82
+ })
83
+ .editSemantic({
84
+ property: `layer[${target}].encoding.${offsetChannel}.fieldType`,
85
+ value: fieldType
86
+ })
87
+ .editSemantic({
88
+ property: `scale[${offsetScale.id}].domain`,
89
+ value: offsetScale.domain
90
+ });
91
+ }
@@ -0,0 +1,88 @@
1
+ import {
2
+ BAR_GRAINS,
3
+ inferBarColorLayout
4
+ } from "../../../grammar/bars/policy.js";
5
+ import { validateColorLayout } from "../../../grammar/seriesLayout.js";
6
+
7
+ export const COLOR_ENCODING_OPTIONS = Object.freeze([
8
+ "field", "target", "fieldType", "scale", "palette", "layout", "aggregate"
9
+ ]);
10
+
11
+ export function resolveColorScaleOptions(args) {
12
+ const options = args.scale ?? {};
13
+ if (args.palette === undefined) return options;
14
+ if (options.palette !== undefined || options.range !== undefined) {
15
+ throw new Error(
16
+ "encodeColor palette cannot be combined with scale.palette or scale.range."
17
+ );
18
+ }
19
+ return { ...options, palette: args.palette };
20
+ }
21
+
22
+ export function assertNoConstantColor(program, layer) {
23
+ const config = program.markConfigs[layer.id];
24
+ const hasConstant = {
25
+ point: config?.fill !== undefined,
26
+ line: config?.stroke !== undefined,
27
+ bar: config?.barAppearance?.fill !== undefined,
28
+ arc: config?.fill !== undefined,
29
+ rect: config?.fillExplicit === true
30
+ }[layer.mark.type] ?? false;
31
+ if (hasConstant) {
32
+ throw new Error(
33
+ `encodeColor cannot replace constant appearance on ${layer.mark.type} mark "${layer.id}".`
34
+ );
35
+ }
36
+ }
37
+
38
+ export function isRangedArea(layer) {
39
+ return layer.mark.type === "area" && (
40
+ layer.encoding?.x2 !== undefined ||
41
+ layer.encoding?.y2 !== undefined
42
+ );
43
+ }
44
+
45
+ export function resolveColorLayout(layer, requested, barGrain) {
46
+ const existing = layer.encoding?.color === undefined
47
+ ? undefined
48
+ : layer.encoding.color.layout ?? (
49
+ layer.mark.type === "bar"
50
+ ? inferBarColorLayout(layer)
51
+ : layer.mark.type === "area"
52
+ ? "overlay"
53
+ : undefined
54
+ );
55
+ if (requested !== undefined) validateColorLayout(requested);
56
+ if (existing !== undefined && requested !== undefined && requested !== existing) {
57
+ throw new Error(
58
+ `Color layout transition from "${existing}" to "${requested}" is not supported.`
59
+ );
60
+ }
61
+ const layout = requested ?? existing ?? (
62
+ layer.mark.type === "bar"
63
+ ? barGrain === BAR_GRAINS.histogram
64
+ ? "stack"
65
+ : barGrain === BAR_GRAINS.ranged ? "overlay" : "group"
66
+ : layer.mark.type === "area"
67
+ ? "overlay"
68
+ : undefined
69
+ );
70
+
71
+ if (["point", "line", "rect"].includes(layer.mark.type) && layout !== undefined) {
72
+ throw new Error(`Color layout is not supported for ${layer.mark.type} marks.`);
73
+ }
74
+ if (layer.mark.type === "area" && layout === "group") {
75
+ throw new Error('Area color layout does not support "group".');
76
+ }
77
+ if (
78
+ layer.mark.type === "arc" &&
79
+ layout !== undefined &&
80
+ layout !== "overlay"
81
+ ) {
82
+ throw new Error('Arc color layout currently supports only "overlay".');
83
+ }
84
+ if (isRangedArea(layer) && layout !== "overlay") {
85
+ throw new Error('Ranged area color encoding supports only "overlay" layout.');
86
+ }
87
+ return layout;
88
+ }
@@ -1,13 +1,14 @@
1
1
  import { registerAppearanceEncodingAction } from "./appearance.js";
2
2
  import { registerBarWidthEncodingAction } from "./barWidth.js";
3
- import { registerColorEncodingAction } from "./color.js";
3
+ import { registerColorEncodingAction } from "./color/index.js";
4
4
  import { registerDensityEncodingAction } from "./density.js";
5
5
  import { registerHistogramEncodingAction } from "./histogram.js";
6
6
  import { registerOffsetEncodingAction } from "./offset.js";
7
- import { registerPositionEncodingActions } from "./position.js";
7
+ import { registerPositionEncodingActions } from "./position/index.js";
8
8
  import { registerRangedEncodingActions } from "./ranged.js";
9
9
  import { registerStrokeDashEncodingActions } from "./strokeDash.js";
10
10
  import { registerRuleAppearanceEncodingActions } from "./ruleAppearance.js";
11
+ import { registerTextEncodingAction } from "./text.js";
11
12
 
12
13
  export function registerEncodingActions(ProgramClass) {
13
14
  registerPositionEncodingActions(ProgramClass);
@@ -20,4 +21,5 @@ export function registerEncodingActions(ProgramClass) {
20
21
  registerStrokeDashEncodingActions(ProgramClass);
21
22
  registerAppearanceEncodingAction(ProgramClass);
22
23
  registerRuleAppearanceEncodingActions(ProgramClass);
24
+ registerTextEncodingAction(ProgramClass);
23
25
  }
@@ -1,8 +1,8 @@
1
1
  import { action } from "../../core/action.js";
2
2
  import {
3
3
  readNominalField,
4
- validateNominalFieldType
5
- } from "../../grammar/scales.js";
4
+ validateCategoricalFieldType
5
+ } from "../../grammar/scales/index.js";
6
6
  import { resolveOffsetScaleDefinition } from "../scales/definitions.js";
7
7
  import {
8
8
  applyEncodingScale,
@@ -12,6 +12,7 @@ import {
12
12
  } from "./shared.js";
13
13
  import {
14
14
  resolveBarColorLayout,
15
+ resolveBarChannels,
15
16
  resolveBarGrain
16
17
  } from "../../grammar/bars/policy.js";
17
18
  import { normalizeOffsetPadding } from "../../grammar/bars/geometry.js";
@@ -21,84 +22,101 @@ import { planEncodingRematerialization } from "../../materialization/encodings.j
21
22
  const ENCODING_OPTIONS = Object.freeze([
22
23
  "field", "target", "fieldType", "scale", "paddingInner", "paddingOuter"
23
24
  ]);
24
- const encodeXOffset = action(
25
- {
26
- op: "encodeXOffset",
27
- description: "Encode a nominal field within each ordinal x band."
28
- },
29
- function (args = {}) {
30
- validateOptions(args, ENCODING_OPTIONS, "encodeXOffset");
31
- const fieldType = validateNominalFieldType(args.fieldType ?? "nominal");
32
- const { id: target, dataset, layer } = resolveTarget(
33
- this,
34
- args.target,
35
- ["bar"],
36
- "bar mark"
37
- );
25
+ function createOffsetEncoding(channel) {
26
+ const operation = channel === "xOffset" ? "encodeXOffset" : "encodeYOffset";
27
+ const parentChannel = channel === "xOffset" ? "x" : "y";
28
+ return action(
29
+ {
30
+ op: operation,
31
+ description: `Encode a categorical field within each ${parentChannel} category band.`
32
+ },
33
+ function (args = {}) {
34
+ validateOptions(args, ENCODING_OPTIONS, operation);
35
+ const fieldType = validateCategoricalFieldType(args.fieldType ?? "nominal");
36
+ const { id: target, dataset, layer } = resolveTarget(
37
+ this,
38
+ args.target,
39
+ ["bar"],
40
+ "bar mark"
41
+ );
42
+
43
+ const channels = resolveBarChannels(layer);
44
+ if (
45
+ resolveBarGrain(layer) === undefined ||
46
+ channels?.category !== parentChannel
47
+ ) {
48
+ throw new Error(
49
+ `${operation} requires a complete bar with a ${parentChannel} category encoding.`
50
+ );
51
+ }
52
+ if (
53
+ layer.encoding?.color !== undefined &&
54
+ (resolveBarColorLayout(layer) !== "group" ||
55
+ layer.encoding.color.field !== args.field)
56
+ ) {
57
+ throw new Error(
58
+ `${operation} field must match a grouped bar color field.`
59
+ );
60
+ }
38
61
 
39
- if (
40
- resolveBarGrain(layer) === undefined ||
41
- layer.encoding.x.scale === undefined
42
- ) {
43
- throw new Error(
44
- "encodeXOffset requires a complete bar x/y encoding."
62
+ readNominalField(dataset.values, args.field);
63
+ const requestedScale = resolveReassignmentScaleOptions(
64
+ layer.encoding?.[channel],
65
+ args.scale ?? {}
45
66
  );
46
- }
47
- if (
48
- layer.encoding?.color !== undefined &&
49
- (resolveBarColorLayout(layer) !== "group" ||
50
- layer.encoding.color.field !== args.field)
51
- ) {
52
- throw new Error(
53
- "encodeXOffset field must match a grouped bar color field."
67
+ const scale = resolveOffsetScaleDefinition(this, requestedScale, channel);
68
+ if (Object.hasOwn(scale, "unknown")) {
69
+ throw new Error(
70
+ `${channel} scale unknown is not supported for grouped bars.`
71
+ );
72
+ }
73
+ const padding = normalizeOffsetPadding(
74
+ args,
75
+ this.markConfigs[target]?.[channel],
76
+ channel
54
77
  );
55
- }
56
78
 
57
- readNominalField(dataset.values, args.field);
58
- const requestedScale = resolveReassignmentScaleOptions(
59
- layer.encoding?.xOffset,
60
- args.scale ?? {}
61
- );
62
- const scale = resolveOffsetScaleDefinition(this, requestedScale);
63
- if (Object.hasOwn(scale, "unknown")) {
64
- throw new Error("xOffset scale unknown is not supported for grouped bars.");
79
+ let next = this
80
+ .editSemantic({
81
+ property: `layer[${target}].encoding.${channel}.field`,
82
+ value: args.field
83
+ })
84
+ .editSemantic({
85
+ property: `layer[${target}].encoding.${channel}.fieldType`,
86
+ value: fieldType
87
+ })
88
+ .editSemantic({
89
+ property: `layer[${target}].encoding.${channel}.scale`,
90
+ value: scale.id
91
+ })
92
+ ._withMarkConfig(target, {
93
+ ...this.markConfigs[target],
94
+ [channel]: padding
95
+ });
96
+ next = applyEncodingScale(next, scale, requestedScale, {
97
+ reassignment: layer.encoding?.[channel]?.scale === scale.id
98
+ });
99
+ if (layer.encoding?.color === undefined) {
100
+ return next
101
+ .rematerializeScale({ id: scale.id })
102
+ .editGraphics({ target, property: "length", value: 0 });
103
+ }
104
+ return applyMaterializationPlan(
105
+ next,
106
+ planEncodingRematerialization(next, {
107
+ target,
108
+ channel,
109
+ scale: scale.id
110
+ })
111
+ );
65
112
  }
66
- const padding = normalizeOffsetPadding(
67
- args,
68
- this.markConfigs[target]?.xOffset
69
- );
113
+ );
114
+ }
70
115
 
71
- let next = this
72
- .editSemantic({
73
- property: `layer[${target}].encoding.xOffset.field`,
74
- value: args.field
75
- })
76
- .editSemantic({
77
- property: `layer[${target}].encoding.xOffset.fieldType`,
78
- value: fieldType
79
- })
80
- .editSemantic({
81
- property: `layer[${target}].encoding.xOffset.scale`,
82
- value: scale.id
83
- })
84
- ._withMarkConfig(target, {
85
- ...this.markConfigs[target],
86
- xOffset: padding
87
- });
88
- next = applyEncodingScale(next, scale, requestedScale, {
89
- reassignment: layer.encoding?.xOffset?.scale === scale.id
90
- });
91
- return applyMaterializationPlan(
92
- next,
93
- planEncodingRematerialization(next, {
94
- target,
95
- channel: "xOffset",
96
- scale: scale.id
97
- })
98
- );
99
- }
100
- );
116
+ const encodeXOffset = createOffsetEncoding("xOffset");
117
+ const encodeYOffset = createOffsetEncoding("yOffset");
101
118
 
102
119
  export function registerOffsetEncodingAction(ProgramClass) {
103
120
  ProgramClass.prototype.encodeXOffset = encodeXOffset;
121
+ ProgramClass.prototype.encodeYOffset = encodeYOffset;
104
122
  }