@fundar/data-chart-telling 1.16.0 → 1.18.0

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 (330) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/configuration/ConfigVars.svelte +14 -0
  3. package/dist/configuration/ConfigVars.svelte.d.ts +3 -0
  4. package/dist/configuration/config.svelte.d.ts +5 -0
  5. package/dist/configuration/config.svelte.js +20 -6
  6. package/dist/configuration/themes/dark.js +1 -1
  7. package/dist/configuration/themes/editorial.js +1 -1
  8. package/dist/index.d.ts +12 -4
  9. package/dist/index.js +4 -1
  10. package/dist/layout/base/BasePlotLayout.svelte +1 -1
  11. package/dist/layout/figure/Figure.svelte +5 -12
  12. package/dist/layout/figure/FigureChrome.svelte +2 -10
  13. package/dist/layout/geometry/GeometryLayout.svelte +219 -44
  14. package/dist/layout/hover/HoverDefaults.d.ts +52 -0
  15. package/dist/layout/hover/HoverDefaults.js +96 -0
  16. package/dist/layout/hover/HoverLayers.d.ts +35 -0
  17. package/dist/layout/hover/HoverLayers.js +122 -0
  18. package/dist/layout/hover/HoverSettings.js +4 -1
  19. package/dist/layout/hover/HoverShapes.d.ts +44 -0
  20. package/dist/layout/hover/HoverShapes.js +103 -0
  21. package/dist/layout/hover/PlotHover.svelte.d.ts +18 -0
  22. package/dist/layout/hover/PlotHover.svelte.js +54 -1
  23. package/dist/layout/legend/DisabledStyle.d.ts +1 -1
  24. package/dist/layout/legend/DisabledStyle.js +1 -1
  25. package/dist/layout/legend/DiscreteBadgeItem.svelte +1 -1
  26. package/dist/layout/legend/DiscreteSection.svelte +1 -1
  27. package/dist/layout/legend/LegendState.svelte.d.ts +14 -2
  28. package/dist/layout/legend/LegendState.svelte.js +23 -4
  29. package/dist/layout/legend/LegendText.d.ts +4 -1
  30. package/dist/layout/legend/LegendText.js +7 -1
  31. package/dist/layout/margins/PlotMargins.svelte.d.ts +2 -2
  32. package/dist/layout/margins/PlotMargins.svelte.js +2 -2
  33. package/dist/layout/plot/Core.svelte +2 -2
  34. package/dist/layout/plot/Plot.svelte +4 -2
  35. package/dist/layout/plot/Plot.svelte.d.ts +2 -1
  36. package/dist/layout/plot/PublicPlotContext.d.ts +6 -0
  37. package/dist/layout/plot/PublicPlotContext.js +8 -0
  38. package/dist/layout/scales/ScalesLayout.svelte +1 -1
  39. package/dist/layout/styles/ValueLabels.d.ts +43 -4
  40. package/dist/layout/styles/ValueLabels.js +87 -19
  41. package/dist/layout/timeline/Timeline.svelte +5 -1
  42. package/dist/layout/tooltip/TooltipState.svelte.d.ts +8 -9
  43. package/dist/layout/tooltip/TooltipState.svelte.js +28 -29
  44. package/dist/markers/MarkerKinds.d.ts +5 -0
  45. package/dist/markers/MarkerKinds.js +1 -0
  46. package/dist/markers/area/AreaMarker.svelte +3 -0
  47. package/dist/markers/arrow/ArrowMarker.svelte +9 -5
  48. package/dist/markers/band/BandMarker.svelte +74 -0
  49. package/dist/markers/band/BandMarker.svelte.d.ts +7 -0
  50. package/dist/markers/band/Bands.d.ts +16 -0
  51. package/dist/markers/band/Bands.js +48 -0
  52. package/dist/markers/bar/BarMarker.svelte +50 -96
  53. package/dist/markers/bollinger/BollingerMarker.svelte +3 -0
  54. package/dist/markers/box/BoxMarker.svelte +3 -1
  55. package/dist/markers/cell/CellMarker.svelte +11 -7
  56. package/dist/markers/contour/ContourMarker.svelte +3 -0
  57. package/dist/markers/delaunay/DelaunayLinkMarker.svelte +9 -5
  58. package/dist/markers/delaunay/DelaunayMeshMarker.svelte +3 -0
  59. package/dist/markers/density/DensityMarker.svelte +3 -0
  60. package/dist/markers/difference/DifferenceMarker.svelte +3 -0
  61. package/dist/markers/dot/DotMarker.svelte +11 -6
  62. package/dist/markers/geo/GraticuleMarker.svelte +3 -0
  63. package/dist/markers/geo/SphereMarker.svelte +3 -0
  64. package/dist/markers/hover/HoverMarker.svelte +7 -249
  65. package/dist/markers/hover/HoverMarker.svelte.d.ts +4 -27
  66. package/dist/markers/hull/HullMarker.svelte +3 -0
  67. package/dist/markers/image/ImageMarker.svelte +3 -0
  68. package/dist/markers/line/LineMarker.svelte +3 -0
  69. package/dist/markers/link/LinkMarker.svelte +3 -0
  70. package/dist/markers/raster/RasterMarker.svelte +3 -0
  71. package/dist/markers/rect/RectMarker.svelte +25 -17
  72. package/dist/markers/regression/RegressionMarker.svelte +3 -0
  73. package/dist/markers/rule/RuleMarker.svelte +15 -5
  74. package/dist/markers/text/TextFit.d.ts +44 -0
  75. package/dist/markers/text/TextFit.js +86 -0
  76. package/dist/markers/text/TextMarker.svelte +186 -90
  77. package/dist/markers/text/TextMarker.svelte.d.ts +2 -0
  78. package/dist/markers/tick/TickMarker.svelte +9 -3
  79. package/dist/markers/trail/TrailMarker.svelte +3 -0
  80. package/dist/markers/utils/BoxLabel.d.ts +58 -0
  81. package/dist/markers/utils/BoxLabel.js +175 -0
  82. package/dist/markers/utils/MarkerEvents.d.ts +23 -0
  83. package/dist/markers/utils/MarkerEvents.js +44 -0
  84. package/dist/markers/utils/MarkerLabel.svelte +33 -0
  85. package/dist/markers/utils/MarkerLabel.svelte.d.ts +10 -0
  86. package/dist/markers/utils/PointPixel.d.ts +37 -0
  87. package/dist/markers/utils/PointPixel.js +44 -0
  88. package/dist/markers/utils/RowStroke.d.ts +9 -21
  89. package/dist/markers/utils/RowStroke.js +10 -21
  90. package/dist/markers/utils/RowStyle.d.ts +26 -0
  91. package/dist/markers/utils/RowStyle.js +55 -0
  92. package/dist/markers/vector/VectorMarker.svelte +3 -0
  93. package/dist/markers/voronoi/VoronoiMarker.svelte +9 -5
  94. package/dist/markers/voronoi/VoronoiMeshMarker.svelte +3 -0
  95. package/dist/markers/waffle/WaffleMarker.svelte +9 -5
  96. package/dist/phases/Pipeline.d.ts +10 -8
  97. package/dist/phases/Pipeline.js +13 -9
  98. package/dist/phases/context/ContextPhase.d.ts +22 -0
  99. package/dist/phases/context/ContextPhase.js +23 -0
  100. package/dist/phases/context/RowContext.d.ts +35 -0
  101. package/dist/phases/context/RowContext.js +95 -0
  102. package/dist/phases/data/DataPhase.d.ts +4 -4
  103. package/dist/phases/data/DataPhase.js +53 -12
  104. package/dist/phases/hover/HoverPhase.d.ts +6 -7
  105. package/dist/phases/hover/HoverPhase.js +28 -15
  106. package/dist/phases/mapping/MappingPhase.d.ts +4 -4
  107. package/dist/phases/mapping/MappingPhase.js +4 -3
  108. package/dist/phases/markers/MarkerStyles.d.ts +26 -0
  109. package/dist/phases/markers/MarkerStyles.js +126 -0
  110. package/dist/phases/markers/MarkersPhase.d.ts +5 -5
  111. package/dist/phases/markers/MarkersPhase.js +8 -6
  112. package/dist/phases/scales/ScalesPhase.d.ts +4 -4
  113. package/dist/phases/scales/ScalesPhase.js +4 -4
  114. package/dist/phases/sharedScales/SharedScalesPhase.d.ts +2 -2
  115. package/dist/phases/sharedScales/SharedScalesPhase.js +2 -2
  116. package/dist/phases/statistics/MarkerGrouping.d.ts +0 -9
  117. package/dist/phases/statistics/MarkerGrouping.js +0 -16
  118. package/dist/phases/statistics/StatisticsPhase.js +1 -1
  119. package/dist/phases/styles/StylesPhase.d.ts +3 -3
  120. package/dist/phases/styles/StylesPhase.js +2 -2
  121. package/dist/phases/tooltip/TooltipPhase.d.ts +4 -4
  122. package/dist/phases/tooltip/TooltipPhase.js +10 -7
  123. package/dist/phases/transforms/Transforms.js +3 -1
  124. package/dist/phases/transforms/TransformsPhase.d.ts +3 -3
  125. package/dist/phases/transforms/TransformsPhase.js +60 -6
  126. package/dist/phases/transforms/methods/TreemapTransform.d.ts +28 -0
  127. package/dist/phases/transforms/methods/TreemapTransform.js +119 -0
  128. package/dist/plots/correlation/heatmap/defaults.d.ts +2 -2
  129. package/dist/plots/correlation/heatmap/defaults.js +23 -8
  130. package/dist/plots/correlation/heatmap/markers.d.ts +2 -10
  131. package/dist/plots/correlation/heatmap/markers.js +11 -37
  132. package/dist/plots/correlation/scatter/defaults.d.ts +2 -2
  133. package/dist/plots/correlation/scatter/defaults.js +9 -3
  134. package/dist/plots/correlation/scatter/markers.d.ts +3 -3
  135. package/dist/plots/correlation/scatter/markers.js +6 -5
  136. package/dist/plots/distribution/box/BoxSummaries.d.ts +17 -8
  137. package/dist/plots/distribution/box/BoxSummaries.js +90 -40
  138. package/dist/plots/distribution/box/defaults.d.ts +2 -2
  139. package/dist/plots/distribution/box/defaults.js +9 -6
  140. package/dist/plots/distribution/box/markers.d.ts +2 -2
  141. package/dist/plots/distribution/box/markers.js +3 -3
  142. package/dist/plots/distribution/density/defaults.d.ts +2 -2
  143. package/dist/plots/distribution/density/defaults.js +17 -1
  144. package/dist/plots/distribution/density/markers.d.ts +2 -2
  145. package/dist/plots/distribution/density/markers.js +4 -4
  146. package/dist/plots/distribution/histogram/defaults.d.ts +2 -2
  147. package/dist/plots/distribution/histogram/defaults.js +22 -27
  148. package/dist/plots/distribution/histogram/markers.d.ts +3 -12
  149. package/dist/plots/distribution/histogram/markers.js +21 -62
  150. package/dist/plots/distribution/pyramid/defaults.d.ts +2 -2
  151. package/dist/plots/distribution/pyramid/defaults.js +7 -4
  152. package/dist/plots/distribution/pyramid/markers.d.ts +2 -12
  153. package/dist/plots/distribution/pyramid/markers.js +16 -66
  154. package/dist/plots/distribution/violin/defaults.d.ts +2 -2
  155. package/dist/plots/distribution/violin/defaults.js +9 -7
  156. package/dist/plots/distribution/violin/markers.d.ts +2 -2
  157. package/dist/plots/distribution/violin/markers.js +4 -4
  158. package/dist/plots/evolution/area/defaults.d.ts +2 -2
  159. package/dist/plots/evolution/area/defaults.js +1 -1
  160. package/dist/plots/evolution/area/markers.d.ts +2 -2
  161. package/dist/plots/evolution/area/markers.js +4 -4
  162. package/dist/plots/evolution/line/defaults.d.ts +2 -2
  163. package/dist/plots/evolution/line/defaults.js +1 -1
  164. package/dist/plots/evolution/line/markers.d.ts +2 -2
  165. package/dist/plots/evolution/line/markers.js +3 -3
  166. package/dist/plots/evolution/slope/defaults.d.ts +2 -2
  167. package/dist/plots/evolution/slope/defaults.js +8 -4
  168. package/dist/plots/evolution/slope/markers.d.ts +2 -2
  169. package/dist/plots/evolution/slope/markers.js +3 -3
  170. package/dist/plots/evolution/sparkline/defaults.d.ts +2 -2
  171. package/dist/plots/evolution/sparkline/defaults.js +5 -2
  172. package/dist/plots/evolution/sparkline/markers.d.ts +2 -2
  173. package/dist/plots/evolution/sparkline/markers.js +3 -3
  174. package/dist/plots/map/geo/Core.svelte +3 -3
  175. package/dist/plots/map/geo/defaults.d.ts +2 -2
  176. package/dist/plots/map/geo/phases/data.d.ts +1 -1
  177. package/dist/plots/map/geo/phases/data.js +1 -1
  178. package/dist/plots/map/geo/phases/hover.d.ts +2 -2
  179. package/dist/plots/map/geo/phases/hover.js +3 -3
  180. package/dist/plots/map/geo/phases/markers.d.ts +2 -2
  181. package/dist/plots/map/geo/phases/markers.js +5 -4
  182. package/dist/plots/map/geo/phases/scales.d.ts +2 -2
  183. package/dist/plots/map/geo/phases/scales.js +2 -2
  184. package/dist/plots/part-of-a-whole/treemap/Plot.svelte +47 -0
  185. package/dist/plots/part-of-a-whole/treemap/Plot.svelte.d.ts +26 -0
  186. package/dist/plots/part-of-a-whole/treemap/defaults.d.ts +10 -0
  187. package/dist/plots/part-of-a-whole/treemap/defaults.js +183 -0
  188. package/dist/plots/ranking/bar/defaults.d.ts +2 -2
  189. package/dist/plots/ranking/bar/defaults.js +15 -9
  190. package/dist/plots/ranking/bar/markers.d.ts +4 -14
  191. package/dist/plots/ranking/bar/markers.js +45 -86
  192. package/dist/plots/ranking/bullet/defaults.d.ts +2 -2
  193. package/dist/plots/ranking/bullet/defaults.js +4 -4
  194. package/dist/plots/ranking/bullet/markers.d.ts +3 -3
  195. package/dist/plots/ranking/bullet/markers.js +11 -12
  196. package/dist/plots/ranking/cleveland/ClevelandMarkers.d.ts +3 -3
  197. package/dist/plots/ranking/cleveland/ClevelandMarkers.js +28 -23
  198. package/dist/plots/ranking/cleveland/defaults.d.ts +2 -2
  199. package/dist/plots/ranking/cleveland/defaults.js +7 -4
  200. package/dist/plots/ranking/dumbbell/defaults.d.ts +2 -2
  201. package/dist/plots/ranking/forest/defaults.d.ts +2 -2
  202. package/dist/plots/ranking/lollipop/defaults.d.ts +2 -2
  203. package/dist/types/configuration/styling.d.ts +31 -2
  204. package/dist/types/layout/context.d.ts +21 -0
  205. package/dist/types/layout/context.js +1 -0
  206. package/dist/types/layout/hover.d.ts +3 -0
  207. package/dist/types/layout/legend.d.ts +8 -1
  208. package/dist/types/layout/mapping.d.ts +12 -3
  209. package/dist/types/layout/scales.d.ts +6 -0
  210. package/dist/types/layout/styles/values.d.ts +8 -2
  211. package/dist/types/layout/tooltip.d.ts +11 -4
  212. package/dist/types/markers/axis/arrow.d.ts +2 -8
  213. package/dist/types/markers/axis/axisBasedMarkersConfig.d.ts +2 -1
  214. package/dist/types/markers/axis/band.d.ts +24 -0
  215. package/dist/types/markers/axis/band.js +1 -0
  216. package/dist/types/markers/axis/bar.d.ts +6 -10
  217. package/dist/types/markers/axis/cell.d.ts +5 -5
  218. package/dist/types/markers/axis/delaunayLink.d.ts +2 -2
  219. package/dist/types/markers/axis/hover.d.ts +60 -15
  220. package/dist/types/markers/axis/rect.d.ts +4 -8
  221. package/dist/types/markers/axis/rule.d.ts +2 -16
  222. package/dist/types/markers/axis/tick.d.ts +2 -2
  223. package/dist/types/markers/axis/voronoi.d.ts +2 -2
  224. package/dist/types/markers/axis/waffle.d.ts +4 -3
  225. package/dist/types/markers/basic/dot.d.ts +2 -2
  226. package/dist/types/markers/basic/text.d.ts +56 -14
  227. package/dist/types/markers/channels/commonMarkerConfig.d.ts +8 -1
  228. package/dist/types/markers/channels/commonMarkerConfigWithArea.d.ts +1 -2
  229. package/dist/types/markers/channels/commonMarkerConfigWithLabel.d.ts +10 -0
  230. package/dist/types/markers/channels/commonMarkerConfigWithLabel.js +1 -0
  231. package/dist/types/markers/channels/commonMarkerEvents.d.ts +17 -0
  232. package/dist/types/markers/channels/commonMarkerEvents.js +13 -0
  233. package/dist/types/markers/common.d.ts +1 -0
  234. package/dist/types/markers/common.js +1 -0
  235. package/dist/types/markers/geo/contour.d.ts +2 -1
  236. package/dist/types/markers/geo/graticule.d.ts +2 -1
  237. package/dist/types/markers/geo/link.d.ts +2 -1
  238. package/dist/types/markers/geo/sphere.d.ts +2 -1
  239. package/dist/types/markers/geo/vector.d.ts +2 -1
  240. package/dist/types/phases/context/phase.d.ts +7 -0
  241. package/dist/types/phases/context/phase.js +1 -0
  242. package/dist/types/phases/data/phase.d.ts +7 -2
  243. package/dist/types/phases/hover/phase.d.ts +10 -5
  244. package/dist/types/phases/index.d.ts +2 -1
  245. package/dist/types/phases/kind.d.ts +11 -8
  246. package/dist/types/phases/mapping/phase.d.ts +2 -2
  247. package/dist/types/phases/markers/phase.d.ts +3 -3
  248. package/dist/types/phases/scales/phase.d.ts +4 -4
  249. package/dist/types/phases/stage.d.ts +3 -3
  250. package/dist/types/phases/state.d.ts +8 -1
  251. package/dist/types/phases/statistics/phase.d.ts +2 -1
  252. package/dist/types/phases/styles/phase.d.ts +3 -2
  253. package/dist/types/phases/transforms/phase.d.ts +13 -2
  254. package/dist/types/plots/constants.d.ts +17 -19
  255. package/dist/types/plots/correlation/heatmap/props.d.ts +3 -3
  256. package/dist/types/plots/correlation/heatmap/state.d.ts +2 -2
  257. package/dist/types/plots/correlation/scatter/props.d.ts +3 -3
  258. package/dist/types/plots/correlation/scatter/state.d.ts +2 -2
  259. package/dist/types/plots/data/common.d.ts +19 -6
  260. package/dist/types/plots/data/geo.d.ts +1 -1
  261. package/dist/types/plots/distribution/box/props.d.ts +25 -3
  262. package/dist/types/plots/distribution/box/state.d.ts +6 -17
  263. package/dist/types/plots/distribution/density/props.d.ts +3 -11
  264. package/dist/types/plots/distribution/density/state.d.ts +2 -2
  265. package/dist/types/plots/distribution/histogram/props.d.ts +16 -11
  266. package/dist/types/plots/distribution/histogram/state.d.ts +2 -2
  267. package/dist/types/plots/distribution/pyramid/props.d.ts +3 -3
  268. package/dist/types/plots/distribution/pyramid/state.d.ts +2 -2
  269. package/dist/types/plots/distribution/violin/props.d.ts +4 -3
  270. package/dist/types/plots/distribution/violin/state.d.ts +2 -2
  271. package/dist/types/plots/evolution/area/props.d.ts +3 -3
  272. package/dist/types/plots/evolution/area/state.d.ts +2 -2
  273. package/dist/types/plots/evolution/line/props.d.ts +3 -3
  274. package/dist/types/plots/evolution/line/state.d.ts +2 -2
  275. package/dist/types/plots/evolution/slope/state.d.ts +2 -2
  276. package/dist/types/plots/evolution/sparkline/state.d.ts +2 -2
  277. package/dist/types/plots/map/geo/state.d.ts +1 -1
  278. package/dist/types/plots/part-of-a-whole/treemap/props.d.ts +29 -0
  279. package/dist/types/plots/part-of-a-whole/treemap/props.js +1 -0
  280. package/dist/types/plots/part-of-a-whole/treemap/state.d.ts +6 -0
  281. package/dist/types/plots/part-of-a-whole/treemap/state.js +1 -0
  282. package/dist/types/plots/props.d.ts +10 -4
  283. package/dist/types/plots/ranking/bar/props.d.ts +3 -3
  284. package/dist/types/plots/ranking/bar/state.d.ts +2 -2
  285. package/dist/types/plots/ranking/bullet/state.d.ts +2 -2
  286. package/dist/types/plots/ranking/cleveland/props.d.ts +3 -3
  287. package/dist/types/plots/ranking/cleveland/state.d.ts +2 -2
  288. package/dist/types/plots/ranking/dumbbell/props.d.ts +3 -3
  289. package/dist/types/plots/ranking/dumbbell/state.d.ts +2 -2
  290. package/dist/types/plots/ranking/forest/props.d.ts +3 -3
  291. package/dist/types/plots/ranking/forest/state.d.ts +2 -2
  292. package/dist/types/plots/ranking/lollipop/props.d.ts +3 -3
  293. package/dist/types/plots/ranking/lollipop/state.d.ts +2 -2
  294. package/dist/types/plots/styling.d.ts +24 -0
  295. package/dist/types/transforms/index.d.ts +3 -1
  296. package/dist/types/transforms/treemap.d.ts +32 -0
  297. package/dist/types/transforms/treemap.js +5 -0
  298. package/dist/utils/color/ColorTones.d.ts +19 -0
  299. package/dist/utils/color/ColorTones.js +64 -0
  300. package/dist/utils/color/CssColor.d.ts +14 -5
  301. package/dist/utils/color/CssColor.js +69 -27
  302. package/dist/utils/color/index.d.ts +4 -0
  303. package/dist/utils/color/index.js +4 -0
  304. package/dist/utils/grouping.d.ts +18 -1
  305. package/dist/utils/grouping.js +31 -0
  306. package/docs/concepts.md +3 -2
  307. package/docs/legend.md +8 -5
  308. package/docs/markers.md +110 -28
  309. package/docs/plots/evolution/line.md +1 -1
  310. package/docs/plots/evolution/slope.md +1 -1
  311. package/docs/plots/part-of-a-whole/treemap.md +103 -0
  312. package/docs/plots/ranking/bar.md +2 -1
  313. package/docs/plots/ranking/cleveland.md +1 -1
  314. package/docs/plots.md +21 -20
  315. package/docs/scales.md +3 -0
  316. package/docs/state.md +34 -1
  317. package/docs/styles-and-segments.md +20 -19
  318. package/docs/theming.md +6 -12
  319. package/docs/tooltip-and-hover.md +81 -3
  320. package/docs/transforms-and-statistics.md +17 -16
  321. package/examples/LineDates.svelte +1 -1
  322. package/examples/ResponsiveLine.svelte +3 -1
  323. package/examples/Treemap.svelte +13 -0
  324. package/examples/TreemapHover.svelte +25 -0
  325. package/examples/ValueLabels.svelte +1 -1
  326. package/examples/datasets/economic-activity.ts +40 -0
  327. package/examples/datasets/index.ts +1 -0
  328. package/llms-full.txt +11 -9
  329. package/llms.txt +4 -1
  330. package/package.json +3 -1
@@ -0,0 +1,119 @@
1
+ import { hierarchy, treemap, treemapBinary, treemapDice, treemapSlice, treemapSliceDice, treemapSquarify } from 'd3-hierarchy';
2
+ import { resolveAccessor } from '../../../utils/accessors';
3
+ import { TREEMAP_CHANNEL_KEYS } from '../../../types/transforms/treemap';
4
+ const TILES = {
5
+ squarify: treemapSquarify,
6
+ binary: treemapBinary,
7
+ slice: treemapSlice,
8
+ dice: treemapDice,
9
+ sliceDice: treemapSliceDice
10
+ };
11
+ /** What a treemap leaves on each row it produces, and the channels it reads them back by. */
12
+ export const TREEMAP_ROW = {
13
+ /** The tile's name: the value at its last level. */
14
+ name: Symbol('treemap.name'),
15
+ /** The value at every level of the tile, outermost first. */
16
+ path: Symbol('treemap.path'),
17
+ /** The tile's summed `value`. */
18
+ value: Symbol('treemap.value'),
19
+ /** The tile's part of the whole, from `0` to `1`. */
20
+ share: Symbol('treemap.share'),
21
+ x1: Symbol('treemap.x1'),
22
+ x2: Symbol('treemap.x2'),
23
+ y1: Symbol('treemap.y1'),
24
+ y2: Symbol('treemap.y2'),
25
+ x: Symbol('treemap.x'),
26
+ y: Symbol('treemap.y')
27
+ };
28
+ const read = (channel) => typeof channel === 'function'
29
+ ? channel
30
+ : (row) => row[channel];
31
+ /** A treemap as a transform: the layout of d3-hierarchy, in the shape every other transform returns. */
32
+ export class TreemapTransform {
33
+ static adapter = {
34
+ channelKeys: TREEMAP_CHANNEL_KEYS,
35
+ run: (channels, options) => TreemapTransform.layout(channels, options)
36
+ };
37
+ /**
38
+ * A row per tile of `channels.data`, biggest first within its group, placed in the unit square. A row whose
39
+ * value is not a positive number has no area, so it makes no tile. The layout is `aspect` wide while it is
40
+ * computed and scaled back after, so its tiles are shaped after the box they fill.
41
+ */
42
+ static layout(channels, options) {
43
+ const { data, value, ...rest } = channels;
44
+ if (!data)
45
+ throw new Error('treemap: missing data');
46
+ if (value === undefined)
47
+ throw new Error('treemap: missing value channel');
48
+ const size = read(value);
49
+ const levels = (options.levels ?? []).map((level) => resolveAccessor(level));
50
+ const tile = options.tile ?? 'squarify';
51
+ const aspect = Number(options.aspect);
52
+ const width = aspect > 0 && Number.isFinite(aspect) ? aspect : 1;
53
+ const root = { name: '', path: [], rows: [], children: [] };
54
+ const nodes = new Map();
55
+ const nodeOf = (path) => {
56
+ let parent = root;
57
+ for (let depth = 1; depth <= path.length; depth++) {
58
+ const key = JSON.stringify(path.slice(0, depth));
59
+ let node = nodes.get(key);
60
+ if (!node) {
61
+ node = { name: path[depth - 1], path: path.slice(0, depth), rows: [], children: [] };
62
+ nodes.set(key, node);
63
+ parent.children.push(node);
64
+ }
65
+ parent = node;
66
+ }
67
+ return parent;
68
+ };
69
+ for (const row of data) {
70
+ const area = Number(size(row));
71
+ if (!(Number.isFinite(area) && area > 0))
72
+ continue;
73
+ const leaf = nodeOf(levels.map((level) => String(level(row))));
74
+ leaf.rows.push(row);
75
+ leaf.value = (leaf.value ?? 0) + area;
76
+ }
77
+ const tiles = [];
78
+ if (root.children.length > 0 || root.value !== undefined) {
79
+ const tree = hierarchy(root, (node) => (node.children?.length ? node.children : null))
80
+ .sum((node) => node.value ?? 0)
81
+ .sort((a, b) => (b.value ?? 0) - (a.value ?? 0));
82
+ const total = tree.value ?? 0;
83
+ treemap().size([width, 1]).tile(TILES[tile]).round(false)(tree);
84
+ for (const leaf of tree.leaves()) {
85
+ const at = leaf;
86
+ const { rows, path, name } = leaf.data;
87
+ const sum = leaf.value ?? 0;
88
+ const first = rows[0];
89
+ const x1 = at.x0 / width;
90
+ const x2 = at.x1 / width;
91
+ tiles.push({
92
+ ...first,
93
+ // A string `value` names the field the tile's sum reads back from.
94
+ ...(typeof value === 'string' ? { [value]: sum } : {}),
95
+ [TREEMAP_ROW.name]: name,
96
+ [TREEMAP_ROW.path]: path,
97
+ [TREEMAP_ROW.value]: sum,
98
+ [TREEMAP_ROW.share]: total > 0 ? sum / total : 0,
99
+ [TREEMAP_ROW.x1]: x1,
100
+ [TREEMAP_ROW.x2]: x2,
101
+ [TREEMAP_ROW.y1]: at.y0,
102
+ [TREEMAP_ROW.y2]: at.y1,
103
+ [TREEMAP_ROW.x]: (x1 + x2) / 2,
104
+ [TREEMAP_ROW.y]: (at.y0 + at.y1) / 2
105
+ });
106
+ }
107
+ }
108
+ return {
109
+ ...rest,
110
+ data: tiles,
111
+ x: TREEMAP_ROW.x,
112
+ x1: TREEMAP_ROW.x1,
113
+ x2: TREEMAP_ROW.x2,
114
+ y: TREEMAP_ROW.y,
115
+ y1: TREEMAP_ROW.y1,
116
+ y2: TREEMAP_ROW.y2
117
+ };
118
+ }
119
+ }
@@ -1,5 +1,5 @@
1
1
  import type { AxisPlotDefaults } from '../../../types/phases';
2
- import type { HeatmapPlotState, HeatmapPlotCtx } from '../../../types/plots/correlation/heatmap/state';
2
+ import type { HeatmapPlotState, HeatmapPlotEnv } from '../../../types/plots/correlation/heatmap/state';
3
3
  /**
4
4
  * HeatmapPlot on the shared pipeline: both axes are categories. A heatmap's
5
5
  * `color` is each cell's numeric value, which the "data" phase reads as a
@@ -8,4 +8,4 @@ import type { HeatmapPlotState, HeatmapPlotCtx } from '../../../types/plots/corr
8
8
  * cell's tooltip names its coordinates and its value.
9
9
  * Its plot defaults to: hover one cell.
10
10
  */
11
- export declare function buildHeatmapDefaults<TData extends Record<string, unknown>>(): AxisPlotDefaults<HeatmapPlotState<TData>, HeatmapPlotCtx>;
11
+ export declare function buildHeatmapDefaults<TData extends Record<string, unknown>>(): AxisPlotDefaults<HeatmapPlotState<TData>, HeatmapPlotEnv>;
@@ -1,15 +1,17 @@
1
1
  import { ColorRamp } from '../../../utils/color';
2
+ import { HoverDefaults } from '../../../layout/hover/HoverDefaults';
2
3
  import { OpacityScale } from '../../../utils/OpacityScale';
3
4
  import { ContinuousDomain } from '../../../utils/ContinuousDomain';
4
- import { buildMarks, buildValueLabels } from './markers';
5
+ import { buildMarks } from './markers';
6
+ import { RowContext } from '../../../phases/context/RowContext';
5
7
  /**
6
8
  * HeatmapPlot's own "scales" output, over the band axes the shared phase laid
7
9
  * out: the color domain over every cell's value (when `color` is set), and each
8
10
  * row's own resolved color and opacity.
9
11
  */
10
- function extendWithColorScales(state, ctx) {
12
+ function extendWithColorScales(state, env) {
11
13
  const { scales, channels } = state;
12
- const { range, cfg } = ctx;
14
+ const { range, cfg } = env;
13
15
  const { color: colorAcc } = channels;
14
16
  const rows = (state.processedData?.data ?? []);
15
17
  const valueDomain = ContinuousDomain.build(scales.color?.domain, rows.map((d) => Number(colorAcc.accessor(d))), true) ?? [0, 1];
@@ -68,6 +70,14 @@ export function buildHeatmapDefaults() {
68
70
  }
69
71
  })
70
72
  },
73
+ // A cell's value is its color's, not a position's.
74
+ context: (state) => {
75
+ const color = state.channels.color.accessor;
76
+ return ((row) => {
77
+ const value = Number(color(row));
78
+ return { value: Number.isFinite(value) ? value : undefined };
79
+ });
80
+ },
71
81
  statistics: { inheritGrouping: false },
72
82
  sharedScales: { fromScalesPhase: ['color'] },
73
83
  scales: {
@@ -76,13 +86,18 @@ export function buildHeatmapDefaults() {
76
86
  extend: extendWithColorScales
77
87
  },
78
88
  styles: { values: { show: true } },
79
- markers: { marks: buildMarks, valueLabels: buildValueLabels },
89
+ markers: { marks: buildMarks },
80
90
  hover: {
81
- // A hovered cell reads as its formatted value.
91
+ // A cell writes its own value: the hover only restyles the cells - the hovered one outlined.
92
+ marker: (_state, env) => HoverDefaults.shapes(env.cfg),
93
+ // A hovered cell reads as its value, as its value labels write it.
82
94
  label: (state) => {
83
- const format = state.values?.format ?? ((v) => `${v}`);
84
- const color = state.channels.color.accessor;
85
- return ((row) => format(Number(color(row)), ''));
95
+ const format = state.values?.format;
96
+ const number = (v) => `${v}`;
97
+ return ((row) => {
98
+ const context = RowContext.of(row);
99
+ return format ? format(Number(context.value), number, context) : `${context.value}`;
100
+ });
86
101
  },
87
102
  // A cell's color is its value, not a series'.
88
103
  decorate: () => () => ({ series: undefined, color: undefined })
@@ -1,17 +1,9 @@
1
- import type { TextMarkerConfig } from '../../../types/markers/common';
2
- import type { HeatmapPlotState, HeatmapPlotCtx } from '../../../types/plots/correlation/heatmap/state';
3
- import type { ValueLabels } from '../../../layout/styles/ValueLabels';
1
+ import type { HeatmapPlotState, HeatmapPlotEnv } from '../../../types/plots/correlation/heatmap/state';
4
2
  import type { AxisBasedMarkersConfig } from '../../../types/markers/common';
5
- /**
6
- * HeatmapPlot's `styles.values` as one `'text'` marker over the same cells
7
- * the plot draws, each labelled with its own value and centred on it. Shown
8
- * by default.
9
- */
10
- export declare function buildValueLabels<TData extends Record<string, unknown>>(labels: ValueLabels, state: HeatmapPlotState<TData>, ctx: HeatmapPlotCtx): TextMarkerConfig[];
11
3
  /**
12
4
  * HeatmapPlot's "markers" phase: one `'cell'` marker per resolved-style
13
5
  * bucket - svelteplot's opacity/numeric scale machinery requires
14
6
  * `fillOpacity`/`strokeWidth` to be constant per mark instance, unlike
15
7
  * `fill`, which stays a per-row accessor.
16
8
  */
17
- export declare function buildMarks<TData extends Record<string, unknown>>(state: HeatmapPlotState<TData>, ctx: HeatmapPlotCtx): AxisBasedMarkersConfig[];
9
+ export declare function buildMarks<TData extends Record<string, unknown>>(state: HeatmapPlotState<TData>, env: HeatmapPlotEnv): AxisBasedMarkersConfig[];
@@ -8,47 +8,16 @@ function readChannel(channel) {
8
8
  return resolveAccessor(channel);
9
9
  }
10
10
  const OPACITY_STEPS = 32;
11
- /**
12
- * HeatmapPlot's `styles.values` as one `'text'` marker over the same cells
13
- * the plot draws, each labelled with its own value and centred on it. Shown
14
- * by default.
15
- */
16
- export function buildValueLabels(labels, state, ctx) {
17
- const { processedData } = state;
18
- if (!processedData?.data.length)
19
- return [];
20
- const { font, format } = labels;
21
- const getColor = state.channels.color.accessor;
22
- return [
23
- {
24
- type: 'text',
25
- data: processedData.data,
26
- // Read like the cells read them, so the labels add no axis title the cells don't.
27
- x: readChannel(processedData.channels.x),
28
- y: readChannel(processedData.channels.y),
29
- text: (d) => format(Number(getColor(d)), ''),
30
- styles: {
31
- ...font,
32
- fill: font?.fill ?? ctx.cfg.continuous.labelColor,
33
- fontSize: font?.fontSize ?? 12,
34
- fontWeight: font?.fontWeight ?? 600,
35
- dx: font?.dx ?? 0,
36
- dy: font?.dy ?? 0,
37
- textAnchor: labels.textAnchor ?? 'middle',
38
- lineAnchor: font?.lineAnchor ?? 'middle'
39
- }
40
- }
41
- ];
42
- }
43
11
  /**
44
12
  * HeatmapPlot's "markers" phase: one `'cell'` marker per resolved-style
45
13
  * bucket - svelteplot's opacity/numeric scale machinery requires
46
14
  * `fillOpacity`/`strokeWidth` to be constant per mark instance, unlike
47
15
  * `fill`, which stays a per-row accessor.
48
16
  */
49
- export function buildMarks(state, ctx) {
17
+ export function buildMarks(state, env) {
50
18
  const { segments, channels, resolveColor, resolveOpacity } = state;
51
- const { range } = ctx;
19
+ const { range } = env;
20
+ const labels = state.valueLabels?.show ? state.valueLabels : undefined;
52
21
  const getColor = channels.color.accessor;
53
22
  // The cells are the rows the "transforms" phase left, placed by its channels.
54
23
  const processed = state.processedData;
@@ -131,16 +100,21 @@ export function buildMarks(state, ctx) {
131
100
  data: group.data,
132
101
  x: getX,
133
102
  y: getY,
134
- fill: (d) => group.styles.fill ?? resolveColor(d),
135
103
  styles: {
104
+ fill: (d) => group.styles.fill ?? resolveColor(d),
136
105
  fillOpacity: group.styles.fillOpacity ?? group.opacity ?? 1,
137
106
  stroke: group.styles.stroke,
138
107
  strokeWidth: group.styles.strokeWidth ?? 0,
139
108
  strokeOpacity: group.styles.strokeOpacity,
140
109
  strokeDasharray: group.styles.strokeDasharray,
141
- borderRadius: group.styles.borderRadius
110
+ borderRadius: group.styles.borderRadius,
111
+ inset: 0.5
142
112
  },
143
- inset: 0.5
113
+ hover: 'style',
114
+ // Each cell writes its own value, in the middle of it.
115
+ label: labels?.shapeLabel(env, (d) => labels.format(Number(getColor(d)), d), {
116
+ fontWeight: 600
117
+ })
144
118
  }));
145
119
  return markers;
146
120
  }
@@ -1,4 +1,4 @@
1
1
  import type { AxisPlotDefaults } from '../../../types/phases';
2
- import type { ScatterPlotState, ScatterPlotCtx } from '../../../types/plots/correlation/scatter/state';
2
+ import type { ScatterPlotState, ScatterPlotEnv } from '../../../types/plots/correlation/scatter/state';
3
3
  /** ScatterPlot on the shared pipeline: discrete dots, its own radius-padded scales, and its own markers. Its plot defaults to: hover the exact point. */
4
- export declare function buildScatterDefaults<TData extends Record<string, unknown>>(): AxisPlotDefaults<ScatterPlotState<TData>, ScatterPlotCtx>;
4
+ export declare function buildScatterDefaults<TData extends Record<string, unknown>>(): AxisPlotDefaults<ScatterPlotState<TData>, ScatterPlotEnv>;
@@ -1,4 +1,5 @@
1
1
  import { ProcessedPositions } from '../../../phases/transforms/ProcessedPositions';
2
+ import { HoverDefaults } from '../../../layout/hover/HoverDefaults';
2
3
  import { buildMarks, buildValueLabels } from './markers';
3
4
  import { Margins } from '../../../layout/margins/Margins';
4
5
  import { MarkerAxisValues } from '../../../phases/scales/MarkerAxisValues';
@@ -25,7 +26,7 @@ function paddedDomain(values, radiusPx, sizePx) {
25
26
  * the largest dot's radius, and grown to cover what derived markers draw
26
27
  * (a density curve's peak, a regression band).
27
28
  */
28
- function fitDomains(state, ctx) {
29
+ function fitDomains(state, env) {
29
30
  const { scales, channels, groupedSeries } = state;
30
31
  const processed = state.processedData;
31
32
  const position = ProcessedPositions.of(processed);
@@ -39,10 +40,10 @@ function fitDomains(state, ctx) {
39
40
  ? r
40
41
  : r !== undefined
41
42
  ? AUTO_SCALED_RADIUS_ESTIMATE
42
- : Math.max(0, ...groupedSeries.flatMap((group) => group.visualSegments.map((seg) => seg.styles.dotRadius ?? ctx.cfg.scatter.dotRadius)));
43
+ : Math.max(0, ...groupedSeries.flatMap((group) => group.visualSegments.map((seg) => seg.styles.dotRadius ?? env.cfg.scatter.dotRadius)));
43
44
  if (radius <= 0 && !extra.x.length && !extra.y.length)
44
45
  return {};
45
- const box = Margins.resolve({ ...ctx.cfg.margins, ...state.margins }).contentBox(state.width, state.height);
46
+ const box = Margins.resolve({ ...env.cfg.margins, ...state.margins }).contentBox(state.width, state.height);
46
47
  const fit = {};
47
48
  for (const [axis, size] of [
48
49
  ['x', box.width],
@@ -72,6 +73,11 @@ export function buildScatterDefaults() {
72
73
  markers: { marks: buildMarks, valueLabels: buildValueLabels },
73
74
  hover: {
74
75
  strategy: 'punctual',
76
+ // A crosshair through the hovered point, and its label - the point is a dot already.
77
+ marker: () => ({
78
+ back: [HoverDefaults.rule('x'), HoverDefaults.rule('y')],
79
+ front: [HoverDefaults.text()]
80
+ }),
75
81
  label: (state) => {
76
82
  const { x, y } = state.channels;
77
83
  return (row) => `${x.accessor(row)}, ${y.accessor(row)}`;
@@ -1,4 +1,4 @@
1
- import type { ScatterPlotState, ScatterPlotCtx } from '../../../types/plots/correlation/scatter/state';
1
+ import type { ScatterPlotState, ScatterPlotEnv } from '../../../types/plots/correlation/scatter/state';
2
2
  import type { TextMarkerConfig } from '../../../types/markers/common';
3
3
  import { ValueLabels } from '../../../layout/styles/ValueLabels';
4
4
  import type { AxisBasedMarkersConfig } from '../../../types/markers/common';
@@ -8,7 +8,7 @@ import type { AxisBasedMarkersConfig } from '../../../types/markers/common';
8
8
  * sits beside its point and flips to the other side where its text would
9
9
  * cross the plot's edge (`edgeAware`).
10
10
  */
11
- export declare function buildValueLabels<TData extends Record<string, unknown>>(labels: ValueLabels, state: ScatterPlotState<TData>, ctx: ScatterPlotCtx): TextMarkerConfig[];
11
+ export declare function buildValueLabels<TData extends Record<string, unknown>>(labels: ValueLabels, state: ScatterPlotState<TData>): TextMarkerConfig[];
12
12
  /**
13
13
  * ScatterPlot's "markers" phase: one `'dot'` marker per visual segment of the
14
14
  * rows the "transforms" phase left, in their own order, so a reordering
@@ -16,4 +16,4 @@ export declare function buildValueLabels<TData extends Record<string, unknown>>(
16
16
  * "statistics" phase already put in `state.markers` - and its value labels on
17
17
  * top of everything.
18
18
  */
19
- export declare function buildMarks<TData extends Record<string, unknown>>(state: ScatterPlotState<TData>, ctx: ScatterPlotCtx): AxisBasedMarkersConfig[];
19
+ export declare function buildMarks<TData extends Record<string, unknown>>(state: ScatterPlotState<TData>, env: ScatterPlotEnv): AxisBasedMarkersConfig[];
@@ -7,7 +7,7 @@ import { ValueLabels } from '../../../layout/styles/ValueLabels';
7
7
  * sits beside its point and flips to the other side where its text would
8
8
  * cross the plot's edge (`edgeAware`).
9
9
  */
10
- export function buildValueLabels(labels, state, ctx) {
10
+ export function buildValueLabels(labels, state) {
11
11
  const { processedData } = state;
12
12
  if (!processedData?.data.length)
13
13
  return [];
@@ -21,10 +21,10 @@ export function buildValueLabels(labels, state, ctx) {
21
21
  data: processedData.data,
22
22
  x: position.x,
23
23
  y: position.y,
24
- text: (d) => format(Number(valueOf(d)), nameOf(d), d),
24
+ text: (d) => format(Number(valueOf(d)), d, nameOf(d)),
25
25
  styles: {
26
26
  ...font,
27
- fill: font?.fill ?? ctx.cfg.axis.color,
27
+ fill: font?.fill ?? 'currentColor',
28
28
  fontSize: font?.fontSize ?? 11,
29
29
  fontWeight: font?.fontWeight ?? 500,
30
30
  dx: font?.dx ?? 8,
@@ -43,9 +43,9 @@ export function buildValueLabels(labels, state, ctx) {
43
43
  * "statistics" phase already put in `state.markers` - and its value labels on
44
44
  * top of everything.
45
45
  */
46
- export function buildMarks(state, ctx) {
46
+ export function buildMarks(state, env) {
47
47
  const { channels, colorForSeries, scales, flat } = state;
48
- const { cfg, interaction } = ctx;
48
+ const { cfg, interaction } = env;
49
49
  const r = channels.r;
50
50
  const rFn = r === undefined || typeof r === 'number' ? r : r.accessor;
51
51
  const opacityFn = channels.opacity?.accessor;
@@ -98,6 +98,7 @@ export function buildMarks(state, ctx) {
98
98
  markers.push({
99
99
  type: 'dot',
100
100
  data: seg.data,
101
+ hover: 'style',
101
102
  // Forwarded raw (a field key or function, whatever `series.x`/`.y`
102
103
  // is) rather than pre-resolved into a closure - `DotMarker.svelte`
103
104
  // and svelteplot's own `resolve()` already handle a field-key
@@ -1,19 +1,28 @@
1
- import type { HoverPosition } from '../../../types/phases';
1
+ import type { HoverPosition, ProcessedRow } from '../../../types/phases';
2
2
  import type { BoxPlotState, BoxSummaryRow } from '../../../types/plots/distribution/box/state';
3
+ import type { BoxSummary } from '../../../types/plots/distribution/box/props';
3
4
  /**
4
- * The boxes a box plot draws, one summary row per series, category and facet
5
- * panel - the same quartiles and whiskers svelteplot's `BoxX`/`BoxY` compute,
6
- * so its hover and tooltip read a box rather than every value inside it.
5
+ * The boxes a box plot draws, one per series, category and facet panel - the same quartiles and whiskers
6
+ * svelteplot's `BoxX`/`BoxY` compute, worked out once as each value's context ({@link context}), so its hover and
7
+ * tooltip read a box rather than every value inside it ({@link of}).
7
8
  */
8
9
  export declare class BoxSummaries {
9
- /** One summary per box, in the order the processed rows first reach it. */
10
+ #private;
11
+ /**
12
+ * Each value's context: the summary of the box it is in - its category, quartiles, whisker ends (the farthest
13
+ * values within 1.5 IQR of the box), how many values it has and the ones past the whiskers.
14
+ */
15
+ static context<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): (row: ProcessedRow) => BoxSummary | undefined;
16
+ /**
17
+ * One summary row per box, in the order the processed rows first reach it, read off its first value's context.
18
+ * Its own context is the box's, with the median for its value, and where it sits for `x` and `y`.
19
+ */
10
20
  static of<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): BoxSummaryRow[];
11
21
  /** Where a box's summary sits for hover: at its category, level with its median. */
12
22
  static position<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): HoverPosition;
13
23
  /**
14
- * How a hovered box reads by default: its series, when the plot groups its
15
- * rows; its category; then its five numbers, highest first, and how many
16
- * values fall past the whiskers.
24
+ * How a hovered box reads by default, from its context: its series, when the plot groups its rows; its
25
+ * category; then its five numbers, highest first, and how many values fall past the whiskers.
17
26
  */
18
27
  static format<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): (row: BoxSummaryRow) => string;
19
28
  }
@@ -4,28 +4,37 @@ import { SERIES_ID_KEY, SERIES_KEY } from '../../../phases/transforms/Transforms
4
4
  import { PANEL_KEY } from '../../../phases/facet/FacetPanels';
5
5
  import { PlotAxes } from '../../../phases/data/PlotAxes';
6
6
  import { TooltipState } from '../../../layout/tooltip/TooltipState.svelte';
7
+ import { RowContext } from '../../../phases/context/RowContext';
7
8
  import { isGapValue } from '../../utils/gaps';
8
9
  /** The whiskers reach this many interquartile ranges past the box, as svelteplot draws them. */
9
10
  const WHISKER_IQR = 1.5;
10
11
  /** A summary value as a tooltip shows it: at most two decimals. */
11
12
  const round = (value) => String(Math.round(value * 100) / 100);
12
13
  /**
13
- * The boxes a box plot draws, one summary row per series, category and facet
14
- * panel - the same quartiles and whiskers svelteplot's `BoxX`/`BoxY` compute,
15
- * so its hover and tooltip read a box rather than every value inside it.
14
+ * The boxes a box plot draws, one per series, category and facet panel - the same quartiles and whiskers
15
+ * svelteplot's `BoxX`/`BoxY` compute, worked out once as each value's context ({@link context}), so its hover and
16
+ * tooltip read a box rather than every value inside it ({@link of}).
16
17
  */
17
18
  export class BoxSummaries {
18
- /** One summary per box, in the order the processed rows first reach it. */
19
- static of(state) {
19
+ /** Which box a processed row is in: its series, panel and category. */
20
+ static #keyOf(state) {
21
+ const categoryAxis = PlotAxes.categoryOf(state);
22
+ const categoryOf = ProcessedPositions.of(state.processedData, PlotAxes.valueOf(state))[categoryAxis];
23
+ return (row) => JSON.stringify([row[SERIES_ID_KEY], row[PANEL_KEY] ?? null, String(categoryOf(row))]);
24
+ }
25
+ /**
26
+ * Each value's context: the summary of the box it is in - its category, quartiles, whisker ends (the farthest
27
+ * values within 1.5 IQR of the box), how many values it has and the ones past the whiskers.
28
+ */
29
+ static context(state) {
20
30
  const processed = state.processedData;
21
31
  if (!processed?.data.length)
22
- return [];
32
+ return () => undefined;
23
33
  const valueAxis = PlotAxes.valueOf(state);
24
- const categoryAxis = PlotAxes.categoryOf(state);
25
34
  const position = ProcessedPositions.of(processed, valueAxis);
26
35
  const valueOf = position[valueAxis];
27
- const categoryOf = position[categoryAxis];
28
- const categoryField = processed.channels[categoryAxis];
36
+ const categoryOf = position[PlotAxes.categoryOf(state)];
37
+ const keyOf = BoxSummaries.#keyOf(state);
29
38
  const boxes = new Map();
30
39
  for (const row of processed.data) {
31
40
  const value = valueOf(row);
@@ -34,39 +43,80 @@ export class BoxSummaries {
34
43
  const n = Number(value);
35
44
  if (!Number.isFinite(n))
36
45
  continue;
37
- const key = JSON.stringify([
38
- row[SERIES_ID_KEY],
39
- row[PANEL_KEY] ?? null,
40
- String(categoryOf(row))
41
- ]);
46
+ const key = keyOf(row);
42
47
  const box = boxes.get(key);
43
48
  if (box)
44
49
  box.values.push(n);
45
50
  else
46
- boxes.set(key, { first: row, values: [n] });
51
+ boxes.set(key, { category: categoryOf(row), values: [n] });
52
+ }
53
+ const summaries = new Map([...boxes].map(([key, { category, values }]) => [
54
+ key,
55
+ BoxSummaries.#summarize(category, values)
56
+ ]));
57
+ return (row) => summaries.get(keyOf(row));
58
+ }
59
+ static #summarize(category, values) {
60
+ const sorted = values.sort((a, b) => a - b);
61
+ const p25 = quantile(sorted, 0.25);
62
+ const median = quantile(sorted, 0.5);
63
+ const p75 = quantile(sorted, 0.75);
64
+ const reach = (p75 - p25) * WHISKER_IQR;
65
+ const inside = sorted.filter((v) => v >= p25 - reach && v <= p75 + reach);
66
+ return {
67
+ category,
68
+ min: inside[0] ?? p25,
69
+ p25,
70
+ median,
71
+ p75,
72
+ max: inside.at(-1) ?? p75,
73
+ count: sorted.length,
74
+ outliers: sorted.filter((v) => v < p25 - reach || v > p75 + reach)
75
+ };
76
+ }
77
+ /**
78
+ * One summary row per box, in the order the processed rows first reach it, read off its first value's context.
79
+ * Its own context is the box's, with the median for its value, and where it sits for `x` and `y`.
80
+ */
81
+ static of(state) {
82
+ const processed = state.processedData;
83
+ if (!processed?.data.length)
84
+ return [];
85
+ const valueAxis = PlotAxes.valueOf(state);
86
+ const categoryAxis = PlotAxes.categoryOf(state);
87
+ const categoryField = processed.channels[categoryAxis];
88
+ const keyOf = BoxSummaries.#keyOf(state);
89
+ const firsts = new Map();
90
+ for (const row of processed.data) {
91
+ const key = keyOf(row);
92
+ if (!firsts.has(key) && RowContext.of(row).median !== undefined)
93
+ firsts.set(key, row);
47
94
  }
48
- return [...boxes.values()].map(({ first, values }) => {
49
- const sorted = values.sort((a, b) => a - b);
50
- const p25 = quantile(sorted, 0.25);
51
- const median = quantile(sorted, 0.5);
52
- const p75 = quantile(sorted, 0.75);
53
- const reach = (p75 - p25) * WHISKER_IQR;
54
- const inside = sorted.filter((v) => v >= p25 - reach && v <= p75 + reach);
55
- const category = categoryOf(first);
56
- return {
95
+ return [...firsts.values()].map((first) => {
96
+ // The box's context, not its first value's row.
97
+ const context = { ...RowContext.of(first), row: undefined };
98
+ const { category, min, p25, median, p75, max, count, outliers } = context;
99
+ const summary = {
57
100
  ...(typeof categoryField === 'string' ? { [categoryField]: category } : {}),
58
101
  [SERIES_KEY]: first[SERIES_KEY],
59
102
  [SERIES_ID_KEY]: first[SERIES_ID_KEY],
60
103
  ...(first[PANEL_KEY] !== undefined ? { [PANEL_KEY]: first[PANEL_KEY] } : {}),
61
104
  category,
62
- min: inside[0] ?? p25,
105
+ min,
63
106
  p25,
64
107
  median,
65
108
  p75,
66
- max: inside.at(-1) ?? p75,
67
- count: sorted.length,
68
- outliers: sorted.filter((v) => v < p25 - reach || v > p75 + reach)
109
+ max,
110
+ count,
111
+ outliers
69
112
  };
113
+ RowContext.tag(summary, {
114
+ ...context,
115
+ [valueAxis]: median,
116
+ [categoryAxis]: category,
117
+ value: median
118
+ });
119
+ return summary;
70
120
  });
71
121
  }
72
122
  /** Where a box's summary sits for hover: at its category, level with its median. */
@@ -79,22 +129,22 @@ export class BoxSummaries {
79
129
  return at;
80
130
  }
81
131
  /**
82
- * How a hovered box reads by default: its series, when the plot groups its
83
- * rows; its category; then its five numbers, highest first, and how many
84
- * values fall past the whiskers.
132
+ * How a hovered box reads by default, from its context: its series, when the plot groups its rows; its
133
+ * category; then its five numbers, highest first, and how many values fall past the whiskers.
85
134
  */
86
135
  static format(state) {
87
136
  const grouped = state.channels?.grouping !== undefined;
88
137
  return (row) => {
89
- const lines = grouped ? [String(row[SERIES_KEY])] : [];
90
- lines.push(TooltipState.value(row.category));
91
- lines.push(`max: ${round(row.max)}`);
92
- lines.push(`p75: ${round(row.p75)}`);
93
- lines.push(`median: ${round(row.median)}`);
94
- lines.push(`p25: ${round(row.p25)}`);
95
- lines.push(`min: ${round(row.min)}`);
96
- if (row.outliers.length)
97
- lines.push(`outliers: ${row.outliers.length}`);
138
+ const box = RowContext.of(row);
139
+ const lines = grouped ? [String(box.series)] : [];
140
+ lines.push(TooltipState.value(box.category));
141
+ lines.push(`max: ${round(box.max)}`);
142
+ lines.push(`p75: ${round(box.p75)}`);
143
+ lines.push(`median: ${round(box.median)}`);
144
+ lines.push(`p25: ${round(box.p25)}`);
145
+ lines.push(`min: ${round(box.min)}`);
146
+ if (box.outliers.length)
147
+ lines.push(`outliers: ${box.outliers.length}`);
98
148
  return lines.join('\n');
99
149
  };
100
150
  }
@@ -1,5 +1,5 @@
1
1
  import type { AxisPlotDefaults } from '../../../types/phases';
2
- import type { BoxPlotState, BoxPlotCtx } from '../../../types/plots/distribution/box/state';
2
+ import type { BoxPlotState, BoxPlotEnv } from '../../../types/plots/distribution/box/state';
3
3
  /**
4
4
  * BoxPlot on the shared pipeline: one box per category of each series along
5
5
  * one category axis - `x`, or `y` for horizontal boxes, as the "data" phase
@@ -9,4 +9,4 @@ import type { BoxPlotState, BoxPlotCtx } from '../../../types/plots/distribution
9
9
  * hover along the category axis, over one summary per box rather than every
10
10
  * value inside it.
11
11
  */
12
- export declare function buildBoxDefaults<TData extends Record<string, unknown>>(): AxisPlotDefaults<BoxPlotState<TData>, BoxPlotCtx>;
12
+ export declare function buildBoxDefaults<TData extends Record<string, unknown>>(): AxisPlotDefaults<BoxPlotState<TData>, BoxPlotEnv>;