ggaction 0.0.13 → 0.0.16

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 (359) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/README.md +41 -12
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/knowledge/task-packet.schema.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +39 -7
  9. package/src/BasicChartProgram.js +3 -1
  10. package/src/ChartProgram.js +2 -0
  11. package/src/accessibility.js +157 -0
  12. package/src/actions/basic.js +2 -2
  13. package/src/actions/boxPlots/components.js +20 -12
  14. package/src/actions/boxPlots/create.js +4 -5
  15. package/src/actions/boxPlots/edit.js +34 -9
  16. package/src/actions/boxPlots/materialize.js +12 -4
  17. package/src/actions/boxPlots/options.js +48 -16
  18. package/src/actions/canvas/actions.js +3 -3
  19. package/src/actions/canvas/fitting.js +1 -1
  20. package/src/actions/categoryOrder/index.js +21 -12
  21. package/src/actions/charts/area.js +6 -2
  22. package/src/actions/charts/bar.js +3 -2
  23. package/src/actions/charts/beeswarm.js +1 -1
  24. package/src/actions/charts/density.js +7 -3
  25. package/src/actions/charts/ecdf.js +6 -3
  26. package/src/actions/charts/endpoints.js +29 -7
  27. package/src/actions/charts/heatmap.js +3 -2
  28. package/src/actions/charts/histogram.js +5 -4
  29. package/src/actions/charts/horizon.js +4 -2
  30. package/src/actions/charts/interval-regression.js +7 -4
  31. package/src/actions/charts/line.js +4 -2
  32. package/src/actions/charts/parallel.js +9 -3
  33. package/src/actions/charts/pie.js +11 -3
  34. package/src/actions/charts/polar.js +17 -6
  35. package/src/actions/charts/radar.js +9 -3
  36. package/src/actions/charts/radial.js +12 -4
  37. package/src/actions/charts/raincloud.js +2 -2
  38. package/src/actions/charts/rug-strip.js +8 -4
  39. package/src/actions/charts/scatter.js +4 -2
  40. package/src/actions/composition/actions.js +29 -24
  41. package/src/actions/coordinates/actions.js +2 -2
  42. package/src/actions/coordinates/edit.js +76 -0
  43. package/src/actions/data/basicBin2d.js +4 -5
  44. package/src/actions/data/bin.js +3 -3
  45. package/src/actions/data/bin2d.js +63 -193
  46. package/src/actions/data/bin2dMaterialize.js +4 -5
  47. package/src/actions/data/box.js +4 -4
  48. package/src/actions/data/complete.js +26 -0
  49. package/src/actions/data/computed.js +4 -4
  50. package/src/actions/data/create.js +7 -14
  51. package/src/actions/data/density.js +15 -23
  52. package/src/actions/data/derived.js +57 -31
  53. package/src/actions/data/ecdf.js +2 -2
  54. package/src/actions/data/edit.js +551 -0
  55. package/src/actions/data/filter.js +28 -22
  56. package/src/actions/data/fold.js +2 -2
  57. package/src/actions/data/gradientProfile.js +2 -2
  58. package/src/actions/data/horizon.js +2 -2
  59. package/src/actions/data/impute.js +26 -0
  60. package/src/actions/data/index.js +62 -47
  61. package/src/actions/data/interval.js +2 -2
  62. package/src/actions/data/intervalEdit.js +26 -18
  63. package/src/actions/data/normalize.js +27 -0
  64. package/src/actions/data/regression.js +21 -27
  65. package/src/actions/data/revise.js +48 -0
  66. package/src/actions/data/revisionBindings.js +125 -0
  67. package/src/actions/data/shared.js +20 -4
  68. package/src/actions/data/stack.js +2 -2
  69. package/src/actions/data/summary.js +3 -3
  70. package/src/actions/data/timeUnit.js +6 -3
  71. package/src/actions/data/window.js +3 -3
  72. package/src/actions/encodings/angle.js +1 -1
  73. package/src/actions/encodings/appearance.js +4 -4
  74. package/src/actions/encodings/barWidth.js +1 -1
  75. package/src/actions/encodings/channels.js +415 -0
  76. package/src/actions/encodings/color/index.js +3 -2
  77. package/src/actions/encodings/density.js +7 -4
  78. package/src/actions/encodings/histogram.js +4 -3
  79. package/src/actions/encodings/horizon.js +6 -8
  80. package/src/actions/encodings/index.js +4 -0
  81. package/src/actions/encodings/offset.js +22 -15
  82. package/src/actions/encodings/parallel.js +4 -5
  83. package/src/actions/encodings/pathOrder.js +6 -11
  84. package/src/actions/encodings/position/apply.js +53 -7
  85. package/src/actions/encodings/position/index.js +14 -6
  86. package/src/actions/encodings/position/policies/bar.js +9 -0
  87. package/src/actions/encodings/position/resolve.js +24 -7
  88. package/src/actions/encodings/ranged.js +5 -5
  89. package/src/actions/encodings/remove.js +13 -9
  90. package/src/actions/encodings/ruleAppearance.js +2 -24
  91. package/src/actions/encodings/seriesLayout.js +2 -1
  92. package/src/actions/encodings/shared.js +82 -14
  93. package/src/actions/encodings/stroke.js +226 -0
  94. package/src/actions/encodings/strokeDash.js +2 -2
  95. package/src/actions/encodings/text.js +1 -1
  96. package/src/actions/errorBands/create.js +34 -20
  97. package/src/actions/errorBands/edit.js +34 -137
  98. package/src/actions/errorBars/create.js +43 -20
  99. package/src/actions/errorBars/edit.js +29 -125
  100. package/src/actions/facets/actions.js +267 -73
  101. package/src/actions/facets/derive.js +99 -28
  102. package/src/actions/facets/guides.js +4 -5
  103. package/src/actions/facets/replay.js +4 -5
  104. package/src/actions/gradientPlots/components.js +15 -8
  105. package/src/actions/gradientPlots/create.js +4 -5
  106. package/src/actions/gradientPlots/edit.js +18 -9
  107. package/src/actions/gradientPlots/materialize.js +5 -3
  108. package/src/actions/gradientPlots/options.js +10 -2
  109. package/src/actions/gradientPlots/rebind.js +4 -5
  110. package/src/actions/guides/applicability.js +8 -2
  111. package/src/actions/guides/axes/axes.js +23 -4
  112. package/src/actions/guides/axes/axis.js +2 -2
  113. package/src/actions/guides/axes/edit.js +2 -2
  114. package/src/actions/guides/axes/labels.js +67 -21
  115. package/src/actions/guides/axes/lines.js +15 -9
  116. package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
  117. package/src/actions/guides/axes/parallel/resolve.js +6 -2
  118. package/src/actions/guides/axes/parallel.js +5 -5
  119. package/src/actions/guides/axes/remove.js +4 -4
  120. package/src/actions/guides/axes/tickGroups.js +6 -5
  121. package/src/actions/guides/axes/ticks.js +19 -6
  122. package/src/actions/guides/axes/titles.js +21 -13
  123. package/src/actions/guides/grids/grid.js +18 -29
  124. package/src/actions/guides/grids/resolve.js +7 -2
  125. package/src/actions/guides/guides.js +20 -4
  126. package/src/actions/guides/legends/blocks.js +426 -0
  127. package/src/actions/guides/legends/categorical/actions.js +138 -52
  128. package/src/actions/guides/legends/categorical/components.js +55 -45
  129. package/src/actions/guides/legends/categorical/index.js +37 -33
  130. package/src/actions/guides/legends/categorical/layout.js +98 -16
  131. package/src/actions/guides/legends/categorical/options.js +2 -2
  132. package/src/actions/guides/legends/categorical/recipes.js +111 -20
  133. package/src/actions/guides/legends/categorical/resolve.js +12 -8
  134. package/src/actions/guides/legends/categorical/symbols.js +104 -52
  135. package/src/actions/guides/legends/continuous/common.js +76 -20
  136. package/src/actions/guides/legends/continuous/gradient.js +95 -158
  137. package/src/actions/guides/legends/continuous/index.js +2 -0
  138. package/src/actions/guides/legends/continuous/interval.js +21 -16
  139. package/src/actions/guides/legends/continuous/opacity.js +37 -83
  140. package/src/actions/guides/legends/continuous/stroke.js +225 -0
  141. package/src/actions/guides/legends/creation.js +14 -1
  142. package/src/actions/guides/legends/edit.js +159 -60
  143. package/src/actions/guides/legends/focused.js +5 -5
  144. package/src/actions/guides/legends/index.js +2 -0
  145. package/src/actions/guides/legends/lane.js +59 -24
  146. package/src/actions/guides/legends/lifecycle.js +19 -8
  147. package/src/actions/guides/legends/remove.js +17 -8
  148. package/src/actions/guides/legends/sampling.js +140 -0
  149. package/src/actions/guides/legends/size.js +100 -108
  150. package/src/actions/guides/legends/strokeWidth.js +38 -92
  151. package/src/actions/guides/legends/target.js +112 -1
  152. package/src/actions/guides/legends/transition.js +165 -16
  153. package/src/actions/guides/polar/axes/facade.js +8 -8
  154. package/src/actions/guides/polar/axes/labels.js +29 -10
  155. package/src/actions/guides/polar/axes/lines.js +19 -19
  156. package/src/actions/guides/polar/axes/shared.js +16 -0
  157. package/src/actions/guides/polar/axes/ticks.js +10 -16
  158. package/src/actions/guides/polar/axes/titles.js +6 -6
  159. package/src/actions/guides/polar/grids.js +12 -14
  160. package/src/actions/guides/polar/resolve.js +12 -8
  161. package/src/actions/index.js +16 -3
  162. package/src/actions/intervals/revision.js +126 -0
  163. package/src/actions/marks/arc/actions.js +80 -22
  164. package/src/actions/marks/area/actions.js +38 -13
  165. package/src/actions/marks/area/materialize.js +17 -1
  166. package/src/actions/marks/bar/create.js +12 -3
  167. package/src/actions/marks/bar/edit.js +21 -4
  168. package/src/actions/marks/bar/materialize.js +34 -40
  169. package/src/actions/marks/index.js +10 -1
  170. package/src/actions/marks/line/actions.js +40 -17
  171. package/src/actions/marks/line/materialize.js +29 -10
  172. package/src/actions/marks/point/create.js +9 -3
  173. package/src/actions/marks/point/edit.js +19 -7
  174. package/src/actions/marks/point/jitter.js +6 -8
  175. package/src/actions/marks/point/materialize.js +48 -22
  176. package/src/actions/marks/point/packing.js +6 -8
  177. package/src/actions/marks/rect/actions.js +36 -15
  178. package/src/actions/marks/references.js +602 -33
  179. package/src/actions/marks/remove.js +67 -41
  180. package/src/actions/marks/rule/actions.js +51 -14
  181. package/src/actions/marks/shared.js +1 -1
  182. package/src/actions/marks/text/actions.js +79 -14
  183. package/src/actions/marks/text/index.js +4 -0
  184. package/src/actions/marks/text/layout.js +157 -17
  185. package/src/actions/marks/text/removal.js +173 -0
  186. package/src/actions/marks/text/selection.js +72 -0
  187. package/src/actions/marks/tick/actions.js +25 -8
  188. package/src/actions/primitives/createGraphics.js +5 -4
  189. package/src/actions/primitives/editGraphics.js +31 -4
  190. package/src/actions/primitives/graphicProperties.js +6 -0
  191. package/src/actions/primitives/semantic.js +11 -3
  192. package/src/actions/primitives/semanticAction.js +90 -14
  193. package/src/actions/primitives/semanticValidation/dataset.js +2 -4
  194. package/src/actions/primitives/semanticValidation/guide.js +1 -1
  195. package/src/actions/primitives/semanticValidation/index.js +9 -1
  196. package/src/actions/primitives/semanticValidation/layer.js +5 -0
  197. package/src/actions/primitives/semanticValidation/scale.js +28 -7
  198. package/src/actions/regression/components.js +36 -25
  199. package/src/actions/regression/create.js +13 -6
  200. package/src/actions/regression/edit.js +24 -11
  201. package/src/actions/resources/index.js +7 -0
  202. package/src/actions/resources/remove.js +173 -0
  203. package/src/actions/scales/channels.js +212 -0
  204. package/src/actions/scales/consumers/common.js +1 -1
  205. package/src/actions/scales/consumers/families.js +3 -2
  206. package/src/actions/scales/consumers/index.js +19 -2
  207. package/src/actions/scales/consumers/seriesLayout.js +28 -14
  208. package/src/actions/scales/create.js +55 -23
  209. package/src/actions/scales/definitions.js +94 -40
  210. package/src/actions/scales/edit.js +67 -12
  211. package/src/actions/scales/editPolicy.js +81 -11
  212. package/src/actions/scales/index.js +6 -0
  213. package/src/actions/scales/materialize.js +5 -4
  214. package/src/actions/scales/offset.js +48 -0
  215. package/src/actions/scales/parallel.js +71 -0
  216. package/src/actions/scales/preview.js +111 -10
  217. package/src/actions/scales/quantitativeColor.js +4 -5
  218. package/src/actions/selection/actions.js +179 -66
  219. package/src/actions/selection/index.js +3 -0
  220. package/src/actions/textMetrics/index.js +56 -0
  221. package/src/actions/theme/actions.js +70 -15
  222. package/src/actions/theme/composition.js +238 -0
  223. package/src/actions/theme/index.js +9 -2
  224. package/src/actions/theme/reconcile.js +313 -121
  225. package/src/actions/theme/state.js +160 -0
  226. package/src/actions/titles/actions.js +8 -8
  227. package/src/actions/titles/resolve.js +3 -3
  228. package/src/actions/violinPlots/create.js +6 -3
  229. package/src/actions/violinPlots/edit.js +2 -1
  230. package/src/core/action.js +117 -35
  231. package/src/core/compositionState.js +6 -1
  232. package/src/core/diagnostics.js +16 -0
  233. package/src/core/extensionRegistry.js +27 -0
  234. package/src/core/font.js +18 -0
  235. package/src/core/identifiers.js +5 -4
  236. package/src/core/immutable.js +10 -3
  237. package/src/core/optionRequirements.js +8 -0
  238. package/src/core/programState.js +21 -1
  239. package/src/core/resourceReferences.js +341 -0
  240. package/src/core/textMetricProfile.js +28 -0
  241. package/src/core/textMetrics.js +24 -3
  242. package/src/core/validation.js +22 -10
  243. package/src/core/vocabulary.js +16 -3
  244. package/src/diagnostics.js +1 -0
  245. package/src/grammar/bars/aggregate.js +4 -1
  246. package/src/grammar/bars/geometry.js +44 -0
  247. package/src/grammar/bin.js +54 -9
  248. package/src/grammar/complete.js +279 -0
  249. package/src/grammar/computed.js +265 -64
  250. package/src/grammar/coordinates.js +37 -0
  251. package/src/grammar/density.js +239 -37
  252. package/src/grammar/displayLabels.js +57 -0
  253. package/src/grammar/facets/dependencies.js +133 -1
  254. package/src/grammar/facets/guides.js +3 -0
  255. package/src/grammar/facets/index.js +7 -47
  256. package/src/grammar/facets/scales.js +15 -18
  257. package/src/grammar/impute.js +341 -0
  258. package/src/grammar/markLabelSelection.js +57 -0
  259. package/src/grammar/markLabels.js +18 -1
  260. package/src/grammar/normalize.js +366 -0
  261. package/src/grammar/pathSeries.js +26 -8
  262. package/src/grammar/pointShapes.js +13 -4
  263. package/src/grammar/polar.js +90 -8
  264. package/src/grammar/regression/index.js +1 -0
  265. package/src/grammar/regression/parameters.js +23 -0
  266. package/src/grammar/roundedRect.js +180 -0
  267. package/src/grammar/rules.js +1 -0
  268. package/src/grammar/scales/colorConsumers.js +20 -1
  269. package/src/grammar/scales/continuous.js +2 -2
  270. package/src/grammar/scales/definition.js +27 -4
  271. package/src/grammar/scales/index.js +1 -0
  272. package/src/grammar/scales/ordinal.js +12 -3
  273. package/src/grammar/scales/policies.js +2 -2
  274. package/src/grammar/scales/size.js +456 -0
  275. package/src/grammar/scales/types.js +8 -0
  276. package/src/grammar/schemas/concreteGraphic.js +14 -2
  277. package/src/grammar/schemas/graphic.js +15 -0
  278. package/src/grammar/schemas/graphicBounds.js +190 -41
  279. package/src/grammar/schemas/semanticPath.js +7 -2
  280. package/src/grammar/statisticalReference.js +103 -0
  281. package/src/grammar/strokeStyle.js +77 -0
  282. package/src/grammar/summary.js +78 -16
  283. package/src/grammar/timeUnit.js +216 -5
  284. package/src/grammar/transformTopology.js +4 -0
  285. package/src/grammar/transforms.js +394 -28
  286. package/src/grammar/weightedStatistics.js +374 -0
  287. package/src/grammar/window.js +221 -16
  288. package/src/layout/aspect.js +80 -0
  289. package/src/layout/facets.js +59 -5
  290. package/src/layout/labels.js +351 -7
  291. package/src/layout/legend.js +4 -4
  292. package/src/layout/legendItems.js +6 -6
  293. package/src/layout/text.js +10 -9
  294. package/src/layout/title.js +9 -9
  295. package/src/materialization/bars/aggregate.js +25 -2
  296. package/src/materialization/bars/histogram.js +84 -24
  297. package/src/materialization/bars/ranged.js +19 -3
  298. package/src/materialization/bars/resolve.js +7 -4
  299. package/src/materialization/composition.js +3 -1
  300. package/src/materialization/coordinateBounds.js +204 -0
  301. package/src/materialization/dependencies.js +36 -0
  302. package/src/materialization/facetGuides/legacyCategorical.js +3 -0
  303. package/src/materialization/facetGuides/placement.js +9 -3
  304. package/src/materialization/facetGuides/preparation.js +13 -8
  305. package/src/materialization/facetHeaders.js +347 -0
  306. package/src/materialization/facets.js +15 -65
  307. package/src/materialization/guides/layout.js +3 -3
  308. package/src/materialization/guides/resources.js +26 -0
  309. package/src/materialization/legends.js +1 -1
  310. package/src/materialization/marks/index.js +11 -5
  311. package/src/materialization/marks/policies.js +2 -1
  312. package/src/materialization/rect.js +25 -12
  313. package/src/materialization/rectConfig.js +5 -1
  314. package/src/materialization/scaleGuideDependencies.js +10 -0
  315. package/src/materialization/scales/map.js +5 -3
  316. package/src/materialization/scales/policies/binnedPosition.js +2 -2
  317. package/src/materialization/scales/policies/offset.js +50 -8
  318. package/src/materialization/scales/policies/series.js +4 -1
  319. package/src/materialization/scales/resolve.js +22 -6
  320. package/src/materialization/selection/items/arc.js +28 -4
  321. package/src/materialization/selection/items/bar.js +30 -3
  322. package/src/materialization/selection/items/common.js +20 -5
  323. package/src/materialization/selection/items/rect.js +28 -4
  324. package/src/materialization/selection/path.js +1 -1
  325. package/src/materialization/selection/point.js +3 -1
  326. package/src/materialization/text.js +265 -14
  327. package/src/materialization/typography.js +31 -0
  328. package/src/mcp/cli.js +5 -3
  329. package/src/persistence/codec.js +65 -0
  330. package/src/persistence/validation.js +185 -0
  331. package/src/persistence.js +74 -0
  332. package/src/renderers/canvas/circle.js +2 -3
  333. package/src/renderers/canvas/index.js +5 -4
  334. package/src/renderers/canvas/line.js +2 -3
  335. package/src/renderers/canvas/native.js +4 -4
  336. package/src/renderers/canvas/path.js +2 -3
  337. package/src/renderers/canvas/rect.js +2 -3
  338. package/src/renderers/canvas/stroke.js +11 -0
  339. package/src/renderers/nativeBackend.js +10 -0
  340. package/src/renderers/options.js +13 -0
  341. package/src/renderers/pdf.js +25 -54
  342. package/src/renderers/png.js +22 -17
  343. package/src/renderers/svg.js +29 -14
  344. package/src/renderers/text.js +1 -11
  345. package/src/selectors/coordinates.js +3 -1
  346. package/src/selectors/datasets.js +38 -5
  347. package/src/selectors/layers.js +10 -4
  348. package/src/selectors/markOwners.js +46 -0
  349. package/src/selectors/scales.js +6 -4
  350. package/src/theme/defaults.js +103 -1
  351. package/src/version.js +2 -0
  352. package/types/accessibility.d.ts +37 -0
  353. package/types/basic.d.ts +10 -2
  354. package/types/diagnostics.d.ts +15 -0
  355. package/types/index.d.ts +90 -0
  356. package/types/pdf.d.ts +9 -0
  357. package/types/persistence.d.ts +7 -0
  358. package/types/png.d.ts +9 -0
  359. package/types/program.d.ts +944 -118
@@ -3,6 +3,7 @@ import {
3
3
  resolveTextBounds
4
4
  } from "../../core/textMetrics.js";
5
5
  import { interpolateNumber } from "../numeric.js";
6
+ import { resolveStrokeDetails } from "../strokeStyle.js";
6
7
  import {
7
8
  findGraphic,
8
9
  findGraphicParent,
@@ -174,15 +175,101 @@ function endpointDirection(start, command, end, reverse = false) {
174
175
  return undefined;
175
176
  }
176
177
 
177
- function includeMiter(bounds, point, incoming, outgoing, strokeExtent) {
178
+ function includeCircle(bounds, point, radius) {
179
+ return includeExtent(
180
+ bounds,
181
+ point.x - radius,
182
+ point.x + radius,
183
+ point.y - radius,
184
+ point.y + radius
185
+ );
186
+ }
187
+
188
+ function includeEndpointCap(
189
+ bounds,
190
+ point,
191
+ direction,
192
+ strokeExtent,
193
+ lineCap,
194
+ atStart
195
+ ) {
196
+ if (lineCap === "butt" || direction === undefined) return bounds;
197
+ if (lineCap === "round") {
198
+ return includeCircle(bounds, point, strokeExtent);
199
+ }
200
+ const sign = atStart ? -1 : 1;
201
+ const center = {
202
+ x: point.x + direction.x * strokeExtent * sign,
203
+ y: point.y + direction.y * strokeExtent * sign
204
+ };
205
+ const normal = { x: -direction.y, y: direction.x };
206
+ return includeExtent(
207
+ bounds,
208
+ center.x - Math.abs(normal.x) * strokeExtent,
209
+ center.x + Math.abs(normal.x) * strokeExtent,
210
+ center.y - Math.abs(normal.y) * strokeExtent,
211
+ center.y + Math.abs(normal.y) * strokeExtent
212
+ );
213
+ }
214
+
215
+ function segmentStrokeBounds(start, end, strokeExtent, lineCap = "butt") {
216
+ const command = { op: "L", x: end.x, y: end.y };
217
+ const direction = endpointDirection(start, command, end);
218
+ let bounds = includeExtent(
219
+ undefined,
220
+ start.x,
221
+ start.x,
222
+ start.y,
223
+ start.y
224
+ );
225
+ bounds = includeExtent(bounds, end.x, end.x, end.y, end.y);
226
+ if (direction === undefined) {
227
+ return lineCap === "butt"
228
+ ? bounds
229
+ : includeCircle(bounds, start, strokeExtent);
230
+ }
231
+ const normal = { x: -direction.y, y: direction.x };
232
+ for (const point of [start, end]) {
233
+ bounds = includeExtent(
234
+ bounds,
235
+ point.x - Math.abs(normal.x) * strokeExtent,
236
+ point.x + Math.abs(normal.x) * strokeExtent,
237
+ point.y - Math.abs(normal.y) * strokeExtent,
238
+ point.y + Math.abs(normal.y) * strokeExtent
239
+ );
240
+ }
241
+ bounds = includeEndpointCap(
242
+ bounds, start, direction, strokeExtent, lineCap, true
243
+ );
244
+ bounds = includeEndpointCap(
245
+ bounds, end, direction, strokeExtent, lineCap, false
246
+ );
247
+ return bounds;
248
+ }
249
+
250
+ function includeJoin(
251
+ bounds,
252
+ point,
253
+ incoming,
254
+ outgoing,
255
+ strokeExtent,
256
+ lineJoin,
257
+ miterLimit
258
+ ) {
178
259
  if (incoming === undefined || outgoing === undefined) return;
179
260
  const cross = incoming.x * outgoing.y - incoming.y * outgoing.x;
180
261
  if (Math.abs(cross) <= EPSILON) return;
262
+ if (lineJoin === "round") {
263
+ includeCircle(bounds, point, strokeExtent);
264
+ return;
265
+ }
266
+ if (lineJoin === "bevel") return;
181
267
  const normalX = -incoming.y - outgoing.y;
182
268
  const normalY = incoming.x + outgoing.x;
183
269
  const normalLength = Math.hypot(normalX, normalY);
270
+ if (!(normalLength > EPSILON)) return;
184
271
  const ratio = 2 / normalLength;
185
- if (ratio > 10 * (1 + EPSILON)) return;
272
+ if (ratio > miterLimit * (1 + EPSILON)) return;
186
273
  const distance = strokeExtent * ratio * (cross > 0 ? -1 : 1) / normalLength;
187
274
  includeExtent(
188
275
  bounds,
@@ -193,16 +280,11 @@ function includeMiter(bounds, point, incoming, outgoing, strokeExtent) {
193
280
  );
194
281
  }
195
282
 
196
- function pathStrokeBounds(commands, bounds, strokeExtent) {
197
- const expanded = box(
198
- bounds.left, bounds.right, bounds.top, bounds.bottom, strokeExtent
199
- );
200
- if (!(strokeExtent > 0)) return expanded;
283
+ function pathStrokeBounds(commands, bounds, strokeExtent, details) {
284
+ if (!(strokeExtent > 0)) return bounds;
201
285
  const start = { x: commands[0].x, y: commands[0].y };
202
286
  let current = start;
203
- let firstDirection;
204
- let previousDirection;
205
- let segmentCount = 0;
287
+ const segments = [];
206
288
  for (const command of commands.slice(1)) {
207
289
  const end = command.op === "Z"
208
290
  ? start
@@ -210,31 +292,94 @@ function pathStrokeBounds(commands, bounds, strokeExtent) {
210
292
  const outgoing = endpointDirection(current, command, end);
211
293
  const incoming = endpointDirection(current, command, end, true);
212
294
  if (outgoing !== undefined || incoming !== undefined) {
213
- if (segmentCount > 0) {
214
- includeMiter(expanded, current, previousDirection, outgoing, strokeExtent);
215
- } else {
216
- firstDirection = outgoing;
217
- }
218
- previousDirection = incoming;
219
- segmentCount += 1;
295
+ segments.push({
296
+ start: current,
297
+ end,
298
+ command,
299
+ outgoing,
300
+ incoming
301
+ });
220
302
  }
221
303
  current = end;
222
304
  if (command.op === "Z") break;
223
305
  }
224
- if (commands.at(-1)?.op === "Z" && segmentCount > 1) {
225
- includeMiter(
306
+ let expanded = bounds;
307
+ for (const segment of segments) {
308
+ let segmentBounds;
309
+ if (segment.command.op === "C") {
310
+ const centerline = resolvePathCommandBounds([
311
+ { op: "M", ...segment.start },
312
+ segment.command
313
+ ]);
314
+ segmentBounds = box(
315
+ centerline.left,
316
+ centerline.right,
317
+ centerline.top,
318
+ centerline.bottom,
319
+ strokeExtent
320
+ );
321
+ } else {
322
+ segmentBounds = segmentStrokeBounds(
323
+ segment.start,
324
+ segment.end,
325
+ strokeExtent,
326
+ "butt"
327
+ );
328
+ }
329
+ expanded = unionBounds([expanded, segmentBounds]);
330
+ }
331
+ for (let index = 1; index < segments.length; index += 1) {
332
+ includeJoin(
333
+ expanded,
334
+ segments[index].start,
335
+ segments[index - 1].incoming,
336
+ segments[index].outgoing,
337
+ strokeExtent,
338
+ details.lineJoin,
339
+ details.miterLimit
340
+ );
341
+ }
342
+ const closed = commands.at(-1)?.op === "Z";
343
+ if (closed && segments.length > 1) {
344
+ includeJoin(
226
345
  expanded,
227
346
  start,
228
- previousDirection,
229
- firstDirection,
230
- strokeExtent
347
+ segments.at(-1).incoming,
348
+ segments[0].outgoing,
349
+ strokeExtent,
350
+ details.lineJoin,
351
+ details.miterLimit
352
+ );
353
+ } else if (!closed && segments.length > 0) {
354
+ expanded = includeEndpointCap(
355
+ expanded,
356
+ segments[0].start,
357
+ segments[0].outgoing,
358
+ strokeExtent,
359
+ details.lineCap,
360
+ true
361
+ );
362
+ expanded = includeEndpointCap(
363
+ expanded,
364
+ segments.at(-1).end,
365
+ segments.at(-1).incoming,
366
+ strokeExtent,
367
+ details.lineCap,
368
+ false
231
369
  );
232
370
  }
233
371
  return expanded;
234
372
  }
235
373
 
236
- function primitiveBounds(type, properties = {}) {
237
- const strokeExtent = (properties.strokeWidth ?? 0) / 2;
374
+ function hasActiveStroke(properties) {
375
+ return typeof properties.stroke === "string" &&
376
+ (properties.strokeWidth ?? 0) > 0;
377
+ }
378
+
379
+ function primitiveBounds(type, properties = {}, profile) {
380
+ const strokeExtent = hasActiveStroke(properties)
381
+ ? properties.strokeWidth / 2
382
+ : 0;
238
383
  if (type === "circle") {
239
384
  if (![properties.x, properties.y, properties.radius].every(Number.isFinite)) {
240
385
  return undefined;
@@ -261,12 +406,11 @@ function primitiveBounds(type, properties = {}) {
261
406
  if (type === "line") {
262
407
  if (![properties.x1, properties.y1, properties.x2, properties.y2]
263
408
  .every(Number.isFinite)) return undefined;
264
- return box(
265
- Math.min(properties.x1, properties.x2),
266
- Math.max(properties.x1, properties.x2),
267
- Math.min(properties.y1, properties.y2),
268
- Math.max(properties.y1, properties.y2),
269
- strokeExtent
409
+ return segmentStrokeBounds(
410
+ { x: properties.x1, y: properties.y1 },
411
+ { x: properties.x2, y: properties.y2 },
412
+ strokeExtent,
413
+ resolveStrokeDetails(properties).lineCap
270
414
  );
271
415
  }
272
416
  if (type === "text") {
@@ -276,14 +420,19 @@ function primitiveBounds(type, properties = {}) {
276
420
  !Number.isFinite(properties.fontSize) ||
277
421
  typeof properties.text !== "string"
278
422
  ) return undefined;
279
- return resolveTextBounds(properties);
423
+ return resolveTextBounds(properties, profile);
280
424
  }
281
425
  if (type === "path") {
282
426
  const commands = properties.commands;
283
427
  if (!Array.isArray(commands)) return undefined;
284
428
  const bounds = resolvePathCommandBounds(commands);
285
429
  if (bounds === undefined) return undefined;
286
- return pathStrokeBounds(commands, bounds, strokeExtent);
430
+ return pathStrokeBounds(
431
+ commands,
432
+ bounds,
433
+ strokeExtent,
434
+ resolveStrokeDetails(properties)
435
+ );
287
436
  }
288
437
  return undefined;
289
438
  }
@@ -320,15 +469,15 @@ function ancestorOffset(graphicSpec, target) {
320
469
  }
321
470
  }
322
471
 
323
- function objectBounds(graphicSpec, id, object, ancestors, x, y) {
472
+ function objectBounds(graphicSpec, id, object, ancestors, x, y, profile) {
324
473
  if (ancestors.has(id)) {
325
474
  throw new Error(`Graphic attachment cycle includes "${id}".`);
326
475
  }
327
476
  ancestors.add(id);
328
477
  let result = object.items === undefined
329
- ? translateBounds(primitiveBounds(object.type, object.properties), x, y)
478
+ ? translateBounds(primitiveBounds(object.type, object.properties, profile), x, y)
330
479
  : unionBounds(object.items.map(item => translateBounds(
331
- primitiveBounds(item.type ?? object.type, item.properties),
480
+ primitiveBounds(item.type ?? object.type, item.properties, profile),
332
481
  x,
333
482
  y
334
483
  )));
@@ -340,7 +489,7 @@ function objectBounds(graphicSpec, id, object, ancestors, x, y) {
340
489
  throw new Error(`Unknown attached graphic "${childId}".`);
341
490
  }
342
491
  const bounds = objectBounds(
343
- graphicSpec, childId, child.object, ancestors, childX, childY
492
+ graphicSpec, childId, child.object, ancestors, childX, childY, profile
344
493
  );
345
494
  if (bounds !== undefined) {
346
495
  result = includeExtent(
@@ -352,23 +501,23 @@ function objectBounds(graphicSpec, id, object, ancestors, x, y) {
352
501
  return result;
353
502
  }
354
503
 
355
- export function resolveConcreteGraphicBounds(graphicSpec, target) {
504
+ export function resolveConcreteGraphicBounds(graphicSpec, target, profile) {
356
505
  const found = requireGraphic(graphicSpec, target);
357
506
  const [x, y] = ancestorOffset(graphicSpec, target);
358
507
  return requireFiniteBounds(found.kind === "item"
359
508
  ? translateBounds(
360
- primitiveBounds(found.object.type ?? found.owner.type, found.object.properties),
509
+ primitiveBounds(found.object.type ?? found.owner.type, found.object.properties, profile),
361
510
  x,
362
511
  y
363
512
  )
364
- : objectBounds(graphicSpec, found.id, found.object, new Set(), x, y));
513
+ : objectBounds(graphicSpec, found.id, found.object, new Set(), x, y, profile));
365
514
  }
366
515
 
367
- export function unionConcreteGraphicBounds(graphicSpec, targets) {
516
+ export function unionConcreteGraphicBounds(graphicSpec, targets, profile) {
368
517
  if (!Array.isArray(targets)) {
369
518
  throw new TypeError("Graphic bounds targets must be an array.");
370
519
  }
371
520
  return unionBounds(targets.map(target =>
372
- resolveConcreteGraphicBounds(graphicSpec, target)
521
+ resolveConcreteGraphicBounds(graphicSpec, target, profile)
373
522
  ));
374
523
  }
@@ -18,6 +18,7 @@ const ENCODING_PATHS = Object.freeze([
18
18
  "encoding.x.bin.maxBins",
19
19
  "encoding.x.bin.step",
20
20
  "encoding.x.bin.boundaries",
21
+ "encoding.x.weight",
21
22
  "encoding.color.layout",
22
23
  "encoding.color.aggregate",
23
24
  "encoding.group.fields",
@@ -77,7 +78,7 @@ const ENTITY_PATHS = Object.freeze({
77
78
  },
78
79
  coordinate: {
79
80
  collection: "coordinates",
80
- properties: new Set(["type"])
81
+ properties: new Set(["type", "aspect", "polarFrame"])
81
82
  }
82
83
  });
83
84
 
@@ -101,6 +102,9 @@ const GUIDE_PATHS = new Set([
101
102
  "legend.color.scale",
102
103
  "legend.color.title",
103
104
  "legend.color.order",
105
+ "legend.stroke.scale",
106
+ "legend.stroke.title",
107
+ "legend.stroke.order",
104
108
  "legend.size.scale",
105
109
  "legend.size.title",
106
110
  "legend.strokeWidth.scale",
@@ -132,6 +136,7 @@ const GUIDE_REMOVABLE_CONTAINERS = new Set([
132
136
  "grid.theta",
133
137
  "grid.radial",
134
138
  "legend.color",
139
+ "legend.stroke",
135
140
  "legend.size",
136
141
  "legend.strokeWidth",
137
142
  "legend.opacity",
@@ -151,7 +156,7 @@ export function parseSemanticPath(property, { allowContainer = false } = {}) {
151
156
 
152
157
  if (allowContainer) {
153
158
  const entityMatch = property.match(
154
- new RegExp(`^(dataset|layer|scale)\\[(${USER_ID_SOURCE})\\]$`)
159
+ new RegExp(`^(dataset|layer|scale|coordinate)\\[(${USER_ID_SOURCE})\\]$`)
155
160
  );
156
161
  if (entityMatch) {
157
162
  const [, kind, id] = entityMatch;
@@ -0,0 +1,103 @@
1
+ import { aggregateRows } from "./aggregate.js";
2
+ import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
3
+
4
+ const STATISTIC_KEYS = Object.freeze(["op", "p"]);
5
+ const SIMPLE_STATISTICS = new Set(["mean", "median", "min", "max"]);
6
+ const POPULATIONS = new Set(["boundData", "visibleItems"]);
7
+ const TRANSFORM_KEYS = Object.freeze(["type", "target"]);
8
+
9
+ function rejectUnknownKeys(value, supported, label) {
10
+ const unknown = Object.keys(value).find(key => !supported.includes(key));
11
+ if (unknown !== undefined) {
12
+ throw new Error(`Unknown ${label} property "${unknown}".`);
13
+ }
14
+ }
15
+
16
+ export function normalizeReferenceStatistic(value, label = "Reference statistic") {
17
+ if (!isPlainObject(value)) {
18
+ throw new TypeError(`${label} must be a plain object.`);
19
+ }
20
+ rejectUnknownKeys(value, STATISTIC_KEYS, label.toLowerCase());
21
+ if (SIMPLE_STATISTICS.has(value.op)) {
22
+ if (Object.hasOwn(value, "p")) {
23
+ throw new Error(`${label} ${value.op} does not accept p.`);
24
+ }
25
+ return cloneAndFreeze({ op: value.op });
26
+ }
27
+ if (value.op !== "quantile") {
28
+ throw new Error(`Unsupported ${label.toLowerCase()} "${value.op ?? "unknown"}".`);
29
+ }
30
+ if (!Number.isFinite(value.p) || value.p < 0 || value.p > 1) {
31
+ throw new RangeError(`${label} quantile p must be between 0 and 1.`);
32
+ }
33
+ return cloneAndFreeze({ op: "quantile", p: value.p });
34
+ }
35
+
36
+ export function normalizeReferenceStatistics(value, count, label) {
37
+ if (!Array.isArray(value) || value.length !== count) {
38
+ throw new TypeError(`${label} requires exactly ${count} statistic${count === 1 ? "" : "s"}.`);
39
+ }
40
+ return Object.freeze(value.map((statistic, index) =>
41
+ normalizeReferenceStatistic(statistic, `${label} statistic ${index + 1}`)
42
+ ));
43
+ }
44
+
45
+ export function normalizeReferencePopulation(value, label = "Reference population") {
46
+ const population = value ?? "boundData";
47
+ if (!POPULATIONS.has(population)) {
48
+ throw new Error(`${label} must be boundData or visibleItems.`);
49
+ }
50
+ return population;
51
+ }
52
+
53
+ export function normalizeReferenceField(value, label = "Reference field") {
54
+ if (value === undefined) return cloneAndFreeze({ kind: "axis" });
55
+ if (typeof value !== "string" || value.length === 0) {
56
+ throw new TypeError(`${label} must be a non-empty string.`);
57
+ }
58
+ return cloneAndFreeze({ kind: "explicit", field: value });
59
+ }
60
+
61
+ export function normalizeStatisticalReferenceTransform({ target } = {}) {
62
+ const transform = { type: "statisticalReference", target };
63
+ validateStatisticalReferenceTransform(transform);
64
+ return cloneAndFreeze(transform);
65
+ }
66
+
67
+ export function validateStatisticalReferenceTransform(transform) {
68
+ if (!isPlainObject(transform)) {
69
+ throw new TypeError("Statistical reference transform must be a plain object.");
70
+ }
71
+ rejectUnknownKeys(
72
+ transform,
73
+ TRANSFORM_KEYS,
74
+ "statistical reference transform"
75
+ );
76
+ if (transform.type !== "statisticalReference") {
77
+ throw new Error(`Unsupported statistical reference transform "${transform.type}".`);
78
+ }
79
+ if (typeof transform.target !== "string" || transform.target.length === 0) {
80
+ throw new TypeError("Statistical reference transform target must be a non-empty string.");
81
+ }
82
+ return transform;
83
+ }
84
+
85
+ export function deriveStatisticalReferenceValues(values, statistics, label) {
86
+ if (!Array.isArray(values) || values.length === 0) {
87
+ throw new Error(`${label} population must contain at least one value.`);
88
+ }
89
+ values.forEach((value, index) => {
90
+ if (!Number.isFinite(value)) {
91
+ throw new TypeError(
92
+ `${label} population value ${index} must be a finite number.`
93
+ );
94
+ }
95
+ });
96
+ return statistics.map(statistic => aggregateRows(
97
+ values.map(value => ({ value })),
98
+ "value",
99
+ statistic.op === "quantile"
100
+ ? { op: "quantile", probability: statistic.p }
101
+ : statistic.op
102
+ ));
103
+ }
@@ -0,0 +1,77 @@
1
+ import { freezeOwned, isPlainObject } from "../core/immutable.js";
2
+
3
+ export const DEFAULT_LINE_CAP = "butt";
4
+ export const DEFAULT_LINE_JOIN = "miter";
5
+ export const DEFAULT_MITER_LIMIT = 10;
6
+
7
+ export const LINE_CAPS = Object.freeze(["butt", "round", "square"]);
8
+ export const LINE_JOINS = Object.freeze(["miter", "round", "bevel"]);
9
+ export const STROKE_STYLE_PROPERTIES = Object.freeze([
10
+ "lineCap",
11
+ "lineJoin",
12
+ "miterLimit"
13
+ ]);
14
+
15
+ function validateEnum(value, values, property, label) {
16
+ if (typeof value !== "string") {
17
+ throw new TypeError(`${label} ${property} must be a string.`);
18
+ }
19
+ if (!values.includes(value)) {
20
+ throw new Error(
21
+ `${label} ${property} must be one of ${values.join(", ")}.`
22
+ );
23
+ }
24
+ return value;
25
+ }
26
+
27
+ export function validateLineCap(value, label = "Stroke style") {
28
+ return validateEnum(value, LINE_CAPS, "lineCap", label);
29
+ }
30
+
31
+ export function validateLineJoin(value, label = "Stroke style") {
32
+ return validateEnum(value, LINE_JOINS, "lineJoin", label);
33
+ }
34
+
35
+ export function validateMiterLimit(value, label = "Stroke style") {
36
+ if (!Number.isFinite(value)) {
37
+ throw new TypeError(`${label} miterLimit must be a finite number.`);
38
+ }
39
+ if (!(value > 0)) {
40
+ throw new RangeError(`${label} miterLimit must be positive.`);
41
+ }
42
+ return value;
43
+ }
44
+
45
+ export function resolveStrokeDetails(request = {}) {
46
+ if (!isPlainObject(request)) {
47
+ throw new TypeError("Stroke style details must be a plain object.");
48
+ }
49
+ return freezeOwned({
50
+ lineCap: request.lineCap === undefined
51
+ ? DEFAULT_LINE_CAP
52
+ : validateLineCap(request.lineCap),
53
+ lineJoin: request.lineJoin === undefined
54
+ ? DEFAULT_LINE_JOIN
55
+ : validateLineJoin(request.lineJoin),
56
+ miterLimit: request.miterLimit === undefined
57
+ ? DEFAULT_MITER_LIMIT
58
+ : validateMiterLimit(request.miterLimit)
59
+ });
60
+ }
61
+
62
+ export function requestedStrokeDetails(request = {}, label = "Stroke style") {
63
+ if (!isPlainObject(request)) {
64
+ throw new TypeError(`${label} details must be a plain object.`);
65
+ }
66
+ const result = {};
67
+ if (Object.hasOwn(request, "lineCap")) {
68
+ result.lineCap = validateLineCap(request.lineCap, label);
69
+ }
70
+ if (Object.hasOwn(request, "lineJoin")) {
71
+ result.lineJoin = validateLineJoin(request.lineJoin, label);
72
+ }
73
+ if (Object.hasOwn(request, "miterLimit")) {
74
+ result.miterLimit = validateMiterLimit(request.miterLimit, label);
75
+ }
76
+ return freezeOwned(result);
77
+ }