@dou-ui/chart 0.0.0-stage → 0.1.0-alpha.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 (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/chart.js +6 -0
  4. package/dist/chart.js.map +1 -0
  5. package/dist/chart.vue_vue_type_script_setup_true_lang.js +4245 -0
  6. package/dist/chart.vue_vue_type_script_setup_true_lang.js.map +1 -0
  7. package/dist/components/area.js +6 -0
  8. package/dist/components/area.js.map +1 -0
  9. package/dist/components/area.vue_vue_type_script_setup_true_lang.js +55 -0
  10. package/dist/components/area.vue_vue_type_script_setup_true_lang.js.map +1 -0
  11. package/dist/components/axis.js +6 -0
  12. package/dist/components/axis.js.map +1 -0
  13. package/dist/components/axis.vue_vue_type_script_setup_true_lang.js +46 -0
  14. package/dist/components/axis.vue_vue_type_script_setup_true_lang.js.map +1 -0
  15. package/dist/components/bar.js +6 -0
  16. package/dist/components/bar.js.map +1 -0
  17. package/dist/components/bar.vue_vue_type_script_setup_true_lang.js +46 -0
  18. package/dist/components/bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  19. package/dist/components/boxplot.js +6 -0
  20. package/dist/components/boxplot.js.map +1 -0
  21. package/dist/components/boxplot.vue_vue_type_script_setup_true_lang.js +42 -0
  22. package/dist/components/boxplot.vue_vue_type_script_setup_true_lang.js.map +1 -0
  23. package/dist/components/brush.js +6 -0
  24. package/dist/components/brush.js.map +1 -0
  25. package/dist/components/brush.vue_vue_type_script_setup_true_lang.js +42 -0
  26. package/dist/components/brush.vue_vue_type_script_setup_true_lang.js.map +1 -0
  27. package/dist/components/funnel.js +6 -0
  28. package/dist/components/funnel.js.map +1 -0
  29. package/dist/components/funnel.vue_vue_type_script_setup_true_lang.js +41 -0
  30. package/dist/components/funnel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  31. package/dist/components/gauge.js +6 -0
  32. package/dist/components/gauge.js.map +1 -0
  33. package/dist/components/gauge.vue_vue_type_script_setup_true_lang.js +91 -0
  34. package/dist/components/gauge.vue_vue_type_script_setup_true_lang.js.map +1 -0
  35. package/dist/components/heatmap.js +6 -0
  36. package/dist/components/heatmap.js.map +1 -0
  37. package/dist/components/heatmap.vue_vue_type_script_setup_true_lang.js +45 -0
  38. package/dist/components/heatmap.vue_vue_type_script_setup_true_lang.js.map +1 -0
  39. package/dist/components/histogram.js +6 -0
  40. package/dist/components/histogram.js.map +1 -0
  41. package/dist/components/histogram.vue_vue_type_script_setup_true_lang.js +61 -0
  42. package/dist/components/histogram.vue_vue_type_script_setup_true_lang.js.map +1 -0
  43. package/dist/components/line.js +6 -0
  44. package/dist/components/line.js.map +1 -0
  45. package/dist/components/line.vue_vue_type_script_setup_true_lang.js +52 -0
  46. package/dist/components/line.vue_vue_type_script_setup_true_lang.js.map +1 -0
  47. package/dist/components/marker.js +6 -0
  48. package/dist/components/marker.js.map +1 -0
  49. package/dist/components/marker.vue_vue_type_script_setup_true_lang.js +31 -0
  50. package/dist/components/marker.vue_vue_type_script_setup_true_lang.js.map +1 -0
  51. package/dist/components/pie.js +6 -0
  52. package/dist/components/pie.js.map +1 -0
  53. package/dist/components/pie.vue_vue_type_script_setup_true_lang.js +42 -0
  54. package/dist/components/pie.vue_vue_type_script_setup_true_lang.js.map +1 -0
  55. package/dist/components/radar.js +6 -0
  56. package/dist/components/radar.js.map +1 -0
  57. package/dist/components/radar.vue_vue_type_script_setup_true_lang.js +45 -0
  58. package/dist/components/radar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  59. package/dist/components/range.js +6 -0
  60. package/dist/components/range.js.map +1 -0
  61. package/dist/components/range.vue_vue_type_script_setup_true_lang.js +27 -0
  62. package/dist/components/range.vue_vue_type_script_setup_true_lang.js.map +1 -0
  63. package/dist/components/scatter.js +6 -0
  64. package/dist/components/scatter.js.map +1 -0
  65. package/dist/components/scatter.vue_vue_type_script_setup_true_lang.js +45 -0
  66. package/dist/components/scatter.vue_vue_type_script_setup_true_lang.js.map +1 -0
  67. package/dist/components/sunburst.js +6 -0
  68. package/dist/components/sunburst.js.map +1 -0
  69. package/dist/components/sunburst.vue_vue_type_script_setup_true_lang.js +36 -0
  70. package/dist/components/sunburst.vue_vue_type_script_setup_true_lang.js.map +1 -0
  71. package/dist/components/tooltip.js +6 -0
  72. package/dist/components/tooltip.js.map +1 -0
  73. package/dist/components/tooltip.vue_vue_type_script_setup_true_lang.js +31 -0
  74. package/dist/components/tooltip.vue_vue_type_script_setup_true_lang.js.map +1 -0
  75. package/dist/components/treemap.js +6 -0
  76. package/dist/components/treemap.js.map +1 -0
  77. package/dist/components/treemap.vue_vue_type_script_setup_true_lang.js +33 -0
  78. package/dist/components/treemap.vue_vue_type_script_setup_true_lang.js.map +1 -0
  79. package/dist/components/waterfall.js +6 -0
  80. package/dist/components/waterfall.js.map +1 -0
  81. package/dist/components/waterfall.vue_vue_type_script_setup_true_lang.js +49 -0
  82. package/dist/components/waterfall.vue_vue_type_script_setup_true_lang.js.map +1 -0
  83. package/dist/components/zoom.js +6 -0
  84. package/dist/components/zoom.js.map +1 -0
  85. package/dist/components/zoom.vue_vue_type_script_setup_true_lang.js +37 -0
  86. package/dist/components/zoom.vue_vue_type_script_setup_true_lang.js.map +1 -0
  87. package/dist/context.js +30 -0
  88. package/dist/context.js.map +1 -0
  89. package/dist/core/bar.js +223 -0
  90. package/dist/core/bar.js.map +1 -0
  91. package/dist/core/box.js +150 -0
  92. package/dist/core/box.js.map +1 -0
  93. package/dist/core/colors.js +58 -0
  94. package/dist/core/colors.js.map +1 -0
  95. package/dist/core/data.js +257 -0
  96. package/dist/core/data.js.map +1 -0
  97. package/dist/core/decimate.js +211 -0
  98. package/dist/core/decimate.js.map +1 -0
  99. package/dist/core/format.js +151 -0
  100. package/dist/core/format.js.map +1 -0
  101. package/dist/core/funnel.js +68 -0
  102. package/dist/core/funnel.js.map +1 -0
  103. package/dist/core/grid.js +87 -0
  104. package/dist/core/grid.js.map +1 -0
  105. package/dist/core/heatmap.js +187 -0
  106. package/dist/core/heatmap.js.map +1 -0
  107. package/dist/core/histogram.js +81 -0
  108. package/dist/core/histogram.js.map +1 -0
  109. package/dist/core/hit.js +115 -0
  110. package/dist/core/hit.js.map +1 -0
  111. package/dist/core/labels.js +141 -0
  112. package/dist/core/labels.js.map +1 -0
  113. package/dist/core/layout.js +1002 -0
  114. package/dist/core/layout.js.map +1 -0
  115. package/dist/core/line.js +230 -0
  116. package/dist/core/line.js.map +1 -0
  117. package/dist/core/marker.js +205 -0
  118. package/dist/core/marker.js.map +1 -0
  119. package/dist/core/model.js +391 -0
  120. package/dist/core/model.js.map +1 -0
  121. package/dist/core/painter.js +79 -0
  122. package/dist/core/painter.js.map +1 -0
  123. package/dist/core/pattern.js +102 -0
  124. package/dist/core/pattern.js.map +1 -0
  125. package/dist/core/pie.js +119 -0
  126. package/dist/core/pie.js.map +1 -0
  127. package/dist/core/radar.js +83 -0
  128. package/dist/core/radar.js.map +1 -0
  129. package/dist/core/scale.js +149 -0
  130. package/dist/core/scale.js.map +1 -0
  131. package/dist/core/scatter.js +263 -0
  132. package/dist/core/scatter.js.map +1 -0
  133. package/dist/core/svg.js +209 -0
  134. package/dist/core/svg.js.map +1 -0
  135. package/dist/core/sync.js +21 -0
  136. package/dist/core/sync.js.map +1 -0
  137. package/dist/core/time.js +217 -0
  138. package/dist/core/time.js.map +1 -0
  139. package/dist/core/tree.js +348 -0
  140. package/dist/core/tree.js.map +1 -0
  141. package/dist/core/warn.js +11 -0
  142. package/dist/core/warn.js.map +1 -0
  143. package/dist/core/waterfall.js +134 -0
  144. package/dist/core/waterfall.js.map +1 -0
  145. package/dist/core/window.js +94 -0
  146. package/dist/core/window.js.map +1 -0
  147. package/dist/define.js +62 -0
  148. package/dist/define.js.map +1 -0
  149. package/dist/index.js +49 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/locale.js +170 -0
  152. package/dist/locale.js.map +1 -0
  153. package/dist/overlay.js +6 -0
  154. package/dist/overlay.js.map +1 -0
  155. package/dist/overlay.vue_vue_type_script_setup_true_lang.js +207 -0
  156. package/dist/overlay.vue_vue_type_script_setup_true_lang.js.map +1 -0
  157. package/dist/pointer.js +6 -0
  158. package/dist/pointer.js.map +1 -0
  159. package/dist/pointer.vue_vue_type_script_setup_true_lang.js +136 -0
  160. package/dist/pointer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  161. package/dist/static.js +6 -0
  162. package/dist/static.js.map +1 -0
  163. package/dist/static.vue_vue_type_script_setup_true_lang.js +34 -0
  164. package/dist/static.vue_vue_type_script_setup_true_lang.js.map +1 -0
  165. package/dist/style.css +1 -0
  166. package/dist/style.d.ts +1 -0
  167. package/dist/style.js +3 -0
  168. package/dist/table.js +6 -0
  169. package/dist/table.js.map +1 -0
  170. package/dist/table.vue_vue_type_script_setup_true_lang.js +52 -0
  171. package/dist/table.vue_vue_type_script_setup_true_lang.js.map +1 -0
  172. package/dist/types/chart.vue.d.ts +24 -0
  173. package/dist/types/components/area.vue.d.ts +16 -0
  174. package/dist/types/components/axis.vue.d.ts +8 -0
  175. package/dist/types/components/bar.vue.d.ts +14 -0
  176. package/dist/types/components/boxplot.vue.d.ts +14 -0
  177. package/dist/types/components/brush.vue.d.ts +22 -0
  178. package/dist/types/components/funnel.vue.d.ts +14 -0
  179. package/dist/types/components/gauge.vue.d.ts +17 -0
  180. package/dist/types/components/heatmap.vue.d.ts +14 -0
  181. package/dist/types/components/histogram.vue.d.ts +14 -0
  182. package/dist/types/components/line.vue.d.ts +16 -0
  183. package/dist/types/components/marker.vue.d.ts +6 -0
  184. package/dist/types/components/pie.vue.d.ts +23 -0
  185. package/dist/types/components/radar.vue.d.ts +16 -0
  186. package/dist/types/components/range.vue.d.ts +12 -0
  187. package/dist/types/components/scatter.vue.d.ts +14 -0
  188. package/dist/types/components/sunburst.vue.d.ts +17 -0
  189. package/dist/types/components/tooltip.vue.d.ts +13 -0
  190. package/dist/types/components/treemap.vue.d.ts +8 -0
  191. package/dist/types/components/waterfall.vue.d.ts +16 -0
  192. package/dist/types/components/zoom.vue.d.ts +18 -0
  193. package/dist/types/context.d.ts +87 -0
  194. package/dist/types/core/bar.d.ts +38 -0
  195. package/dist/types/core/box.d.ts +25 -0
  196. package/dist/types/core/colors.d.ts +16 -0
  197. package/dist/types/core/data.d.ts +72 -0
  198. package/dist/types/core/decimate.d.ts +39 -0
  199. package/dist/types/core/format.d.ts +33 -0
  200. package/dist/types/core/funnel.d.ts +15 -0
  201. package/dist/types/core/grid.d.ts +18 -0
  202. package/dist/types/core/heatmap.d.ts +17 -0
  203. package/dist/types/core/histogram.d.ts +23 -0
  204. package/dist/types/core/hit.d.ts +31 -0
  205. package/dist/types/core/labels.d.ts +45 -0
  206. package/dist/types/core/layout.d.ts +259 -0
  207. package/dist/types/core/line.d.ts +36 -0
  208. package/dist/types/core/marker.d.ts +67 -0
  209. package/dist/types/core/model.d.ts +86 -0
  210. package/dist/types/core/painter.d.ts +34 -0
  211. package/dist/types/core/pattern.d.ts +14 -0
  212. package/dist/types/core/pie.d.ts +66 -0
  213. package/dist/types/core/radar.d.ts +12 -0
  214. package/dist/types/core/scale.d.ts +60 -0
  215. package/dist/types/core/scatter.d.ts +16 -0
  216. package/dist/types/core/series.d.ts +228 -0
  217. package/dist/types/core/svg.d.ts +63 -0
  218. package/dist/types/core/sync.d.ts +8 -0
  219. package/dist/types/core/time.d.ts +20 -0
  220. package/dist/types/core/tree.d.ts +84 -0
  221. package/dist/types/core/warn.d.ts +4 -0
  222. package/dist/types/core/waterfall.d.ts +24 -0
  223. package/dist/types/core/window.d.ts +36 -0
  224. package/dist/types/define.d.ts +115 -0
  225. package/dist/types/index.d.ts +363 -0
  226. package/dist/types/locale.d.ts +100 -0
  227. package/dist/types/overlay.vue.d.ts +95 -0
  228. package/dist/types/pointer.vue.d.ts +53 -0
  229. package/dist/types/static.vue.d.ts +24 -0
  230. package/dist/types/table.vue.d.ts +19 -0
  231. package/dist/types/types.d.ts +555 -0
  232. package/dist/types/zoom-layer.vue.d.ts +41 -0
  233. package/dist/zoom-layer.js +6 -0
  234. package/dist/zoom-layer.js.map +1 -0
  235. package/dist/zoom-layer.vue_vue_type_script_setup_true_lang.js +210 -0
  236. package/dist/zoom-layer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  237. package/package.json +53 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marker.js","names":[],"sources":["../../src/core/marker.ts"],"sourcesContent":["import type { Tone } from 'dou-ui'\nimport { crisp, type Painter, type Rect } from './painter'\nimport type { Scale } from './scale'\nimport type { ChartValueAxis } from './series'\n\n/*\n * Markers: a line at an x or a value (a target, a date), a band between\n * two of them, or a point. Placed on the screen from the chart's scales,\n * drawn with the painter, with their labels as text over the chart.\n */\n\n/** A marker as the chart reads it: x already as a number on the x scale. */\nexport interface MarkerInput {\n /** One x, or the two ends of a band of x; `null` when not given. */\n x: [number, number] | number | null\n y: [number, number] | number | null\n /** The value axis `y` is on. */\n axis: ChartValueAxis\n label?: string\n tone: Tone\n solid: boolean\n}\n\nexport type MarkerShape =\n /** A line down the chart (`vertical`) or across it, at `at`, from `from` to `to`. */\n | { kind: 'line'; vertical: boolean; at: number; from: number; to: number }\n | ({ kind: 'area' } & Rect)\n | { kind: 'point'; x: number; y: number }\n\nexport interface MarkerLabel {\n text: string\n left: number\n top: number\n /** Which end of the text `left` is: its start, its middle or its end. */\n align: 'start' | 'middle' | 'end'\n}\n\nexport interface PlacedMarker {\n shape: MarkerShape\n tone: Tone\n solid: boolean\n label: MarkerLabel | null\n}\n\n/** Height of a label's box, as tick labels. */\nconst LABEL = 16\n/** Room between a label and its line or point, and the plot's edge. */\nconst GAP = 4\n/** A point's dot. */\nexport const MARKER_DOT = 4\n\ninterface PlaceOptions {\n plot: Rect\n /** The x scale; with `horizontal`, it runs down the left side. */\n x: Scale\n /** A value axis' scale; `null` when the chart has no such axis. */\n y: (axis: ChartValueAxis) => Scale | null\n horizontal: boolean\n measure: (text: string) => number\n}\n\n/** Whether `v` is between `from` and `to`, give or take half a pixel. */\nconst inside = (v: number, from: number, to: number) => v >= from - 0.5 && v <= to + 0.5\n\n/** A value (or the ends of a stretch) in px. */\nconst along = (scale: Scale, value: [number, number] | number) =>\n typeof value === 'number'\n ? scale.map(value)\n : ([scale.map(value[0]), scale.map(value[1])] as [number, number])\n\n/** Where each marker is drawn, and its label; markers wholly off the plot are left out. */\nexport function placeMarkers(markers: MarkerInput[], options: PlaceOptions): PlacedMarker[] {\n const { plot, horizontal, measure } = options\n const right = plot.x + plot.width\n const bottom = plot.y + plot.height\n const out: PlacedMarker[] = []\n // the labels placed so far: the next one goes where it doesn't cover them\n const taken: Array<[number, number, number, number]> = []\n const placeLabel = (places: MarkerLabel[], width: number) => {\n const free = places.find((place) => {\n const [x0, y0, x1, y1] = boxOf(place, width)\n return taken.every(([a0, b0, a1, b1]) => x1 <= a0 || x0 >= a1 || y1 <= b0 || y0 >= b1)\n })\n const label = free ?? places[0]!\n taken.push(boxOf(label, width))\n return label\n }\n for (const marker of markers) {\n const yScale = options.y(marker.axis)\n // along x and along the values, in px; with `horizontal` x runs down\n const xs = marker.x === null ? null : along(options.x, marker.x)\n const ys = marker.y === null || !yScale ? null : along(yScale, marker.y)\n if (xs === null && ys === null) continue\n // in screen terms: left to right (u) and top to bottom (v)\n const u = horizontal ? ys : xs\n const v = horizontal ? xs : ys\n let shape: MarkerShape | null = null\n if (typeof u === 'number' && typeof v === 'number') {\n if (inside(u, plot.x, right) && inside(v, plot.y, bottom))\n shape = { kind: 'point', x: u, y: v }\n } else if (typeof u === 'number' && v === null) {\n if (inside(u, plot.x, right))\n shape = { kind: 'line', vertical: true, at: u, from: plot.y, to: bottom }\n } else if (typeof v === 'number' && u === null) {\n if (inside(v, plot.y, bottom))\n shape = { kind: 'line', vertical: false, at: v, from: plot.x, to: right }\n } else {\n // a band (or a box): the stretches given, the whole plot the other way\n const [u0, u1] = Array.isArray(u) ? [Math.min(...u), Math.max(...u)] : [plot.x, right]\n const [v0, v1] = Array.isArray(v) ? [Math.min(...v), Math.max(...v)] : [plot.y, bottom]\n const x = Math.max(plot.x, u0)\n const y = Math.max(plot.y, v0)\n const width = Math.min(right, u1) - x\n const height = Math.min(bottom, v1) - y\n if (width > 0 && height > 0) shape = { kind: 'area', x, y, width, height }\n }\n if (!shape) continue\n const text = marker.label\n out.push({\n shape,\n tone: marker.tone,\n solid: marker.solid,\n label: text ? placeLabel(placesFor(shape, text, measure(text), plot), measure(text)) : null,\n })\n }\n return out\n}\n\n/** Where a marker's label may go, best first: by the line's end, at the band's corner, over the point. */\nfunction placesFor(shape: MarkerShape, text: string, width: number, plot: Rect): MarkerLabel[] {\n const right = plot.x + plot.width\n const bottom = plot.y + plot.height\n /** Further down, one label's height at a time, while in the plot. */\n const down = (first: Omit<MarkerLabel, 'text'>) =>\n [0, 1, 2, 3]\n .map((k) => ({ text, ...first, top: first.top + k * (LABEL + 2) }))\n .filter((place, k) => k === 0 || place.top + LABEL <= bottom)\n if (shape.kind === 'line' && shape.vertical) {\n // at the top, after the line; before it when there's no room after\n const after = shape.at + GAP + width <= right\n return down({\n left: after ? shape.at + GAP : shape.at - GAP,\n top: plot.y,\n align: after ? 'start' : 'end',\n })\n }\n if (shape.kind === 'line') {\n // at the right end, above the line, or under it\n const above = { text, left: right - GAP, top: shape.at - LABEL - 2, align: 'end' } as const\n const under = { ...above, top: shape.at + 2 }\n return above.top >= plot.y - GAP ? [above, under] : [under, above]\n }\n if (shape.kind === 'area') return down({ left: shape.x + GAP, top: shape.y + 2, align: 'start' })\n // over the point, kept in the plot; beside it (a peak: the data is lower there); under it\n const reach = MARKER_DOT + GAP\n const half = width / 2\n const middle = Math.min(Math.max(shape.x, plot.x + half), Math.max(plot.x + half, right - half))\n const over: MarkerLabel = { text, left: middle, top: shape.y - reach - LABEL, align: 'middle' }\n const after: MarkerLabel = {\n text,\n left: shape.x + reach,\n top: shape.y - LABEL / 2,\n align: 'start',\n }\n const before: MarkerLabel = { ...after, left: shape.x - reach, align: 'end' }\n const under: MarkerLabel = { ...over, top: shape.y + reach }\n const sides = shape.x + reach + width <= right ? [after, before] : [before, after]\n return over.top >= plot.y - GAP ? [over, ...sides, under] : [...sides, under, over]\n}\n\n/** The box a label takes, with its backdrop. */\nfunction boxOf(label: MarkerLabel, width: number): [number, number, number, number] {\n const start =\n label.align === 'start'\n ? label.left\n : label.left - (label.align === 'middle' ? width / 2 : width)\n return [start - GAP, label.top, start + width + GAP, label.top + LABEL]\n}\n\nexport interface MarkerColors {\n line: string\n /** A point's dot: the line's color, solid. */\n dot: string\n /** A band. */\n fill: string\n surface: string\n}\n\n/** Draws the bands (`under` the series) or the lines and points (over them). */\nexport function drawMarkers(\n painter: Painter,\n markers: PlacedMarker[],\n colors: (tone: Tone) => MarkerColors,\n dpr: number,\n under: boolean,\n): void {\n for (const marker of markers) {\n const { shape } = marker\n if ((shape.kind === 'area') !== under) continue\n const c = colors(marker.tone)\n painter.save()\n painter.beginPath()\n if (shape.kind === 'area') {\n painter.fillStyle = c.fill\n painter.rect(shape.x, shape.y, shape.width, shape.height)\n painter.fill()\n } else if (shape.kind === 'line') {\n const at = crisp(shape.at, dpr)\n if (shape.vertical) {\n painter.moveTo(at, shape.from)\n painter.lineTo(at, shape.to)\n } else {\n painter.moveTo(shape.from, at)\n painter.lineTo(shape.to, at)\n }\n painter.lineWidth = 1\n painter.strokeStyle = c.line\n if (!marker.solid) painter.setLineDash([4, 3])\n painter.stroke()\n } else {\n // a dot ringed with the surface, so it stands off a line under it\n painter.arc(shape.x, shape.y, MARKER_DOT, 0, Math.PI * 2)\n painter.fillStyle = c.dot\n painter.strokeStyle = c.surface\n painter.lineWidth = 2\n painter.stroke()\n painter.fill()\n }\n painter.restore()\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,391 @@
1
+ import { byIndex, extent, inferXKind, rowCount, xColumn, yColumn } from "./data.js";
2
+ import { waterfallSteps } from "./waterfall.js";
3
+ import { warnOnce } from "./warn.js";
4
+ import { boxesOf } from "./box.js";
5
+ // src/core/model.ts
6
+ /** What a series is called: its `name`, else its `y` key, else its place. */
7
+ function seriesName(input) {
8
+ return input.props.name ?? (typeof input.props.y === "string" ? input.props.y : `${input.index + 1}`);
9
+ }
10
+ var extents = /* @__PURE__ */ new WeakMap();
11
+ function cachedExtent(values) {
12
+ if (!extents.has(values)) extents.set(values, extent(values));
13
+ return extents.get(values);
14
+ }
15
+ /** The first and last number of a sorted column: its extent, without going through it. */
16
+ function ends(values) {
17
+ let a = 0;
18
+ let b = values.length - 1;
19
+ while (a <= b && values[a] !== values[a]) a++;
20
+ while (b >= a && values[b] !== values[b]) b--;
21
+ return a <= b ? [values[a], values[b]] : null;
22
+ }
23
+ var wholes = /* @__PURE__ */ new WeakMap();
24
+ function allIntegers(values) {
25
+ let known = wholes.get(values);
26
+ if (known === void 0) {
27
+ known = true;
28
+ for (let i = 0; i < values.length; i++) {
29
+ const v = values[i];
30
+ if (v === v && !Number.isInteger(v)) {
31
+ known = false;
32
+ break;
33
+ }
34
+ }
35
+ wholes.set(values, known);
36
+ }
37
+ return known;
38
+ }
39
+ var merge = (a, b) => !a ? b : !b ? a : [Math.min(a[0], b[0]), Math.max(a[1], b[1])];
40
+ /**
41
+ * Reads the data of every shown series into columns, lines up categories
42
+ * shared by series with their own data, stacks what stacks, and finds the
43
+ * range of each axis.
44
+ */
45
+ function buildModel(inputs, chart) {
46
+ const shown = inputs.filter((input) => !input.hidden);
47
+ const sourceOf = (input) => ({
48
+ rows: input.props.data ?? chart.data,
49
+ x: input.props.x ?? chart.x
50
+ });
51
+ let xKind = chart.xKind;
52
+ if (shown.some((input) => input.kind.polar)) xKind = "category";
53
+ if (!xKind) {
54
+ xKind = "linear";
55
+ for (const input of shown) {
56
+ const { rows, x } = sourceOf(input);
57
+ if (x !== void 0 && rowCount(rows)) {
58
+ xKind = inferXKind(rows, x);
59
+ break;
60
+ }
61
+ }
62
+ }
63
+ const kind = xKind;
64
+ const series = shown.map((input) => {
65
+ const { rows, x } = sourceOf(input);
66
+ const column = xColumn(rows, x ?? byIndex, x === void 0 ? "linear" : kind);
67
+ const name = seriesName(input);
68
+ if (input.kind.categoryY) {
69
+ const value = input.props.value;
70
+ const values = value === void 0 ? new Float64Array(rowCount(rows)) : yColumn(rows, value);
71
+ const places = xColumn(rows, input.props.y, "category");
72
+ return {
73
+ input,
74
+ name,
75
+ rows,
76
+ axis: input.props.axis ?? chart.axis ?? "y",
77
+ x: column,
78
+ y: values,
79
+ top: places.values,
80
+ base: null,
81
+ stackEnds: null,
82
+ slot: null,
83
+ yColumn: places
84
+ };
85
+ }
86
+ if (input.kind.box) {
87
+ const list = boxesOf(rows, input.props.y, input.props.whisker);
88
+ const n = list.length;
89
+ const [y, top, base] = [
90
+ new Float64Array(n),
91
+ new Float64Array(n),
92
+ new Float64Array(n)
93
+ ];
94
+ list.forEach((box, i) => {
95
+ if (!box) {
96
+ y[i] = top[i] = base[i] = NaN;
97
+ return;
98
+ }
99
+ y[i] = box.median;
100
+ base[i] = Math.min(box.low, ...box.outliers);
101
+ top[i] = Math.max(box.high, ...box.outliers);
102
+ });
103
+ return {
104
+ input,
105
+ name,
106
+ rows,
107
+ axis: input.props.axis ?? chart.axis ?? "y",
108
+ x: column,
109
+ y,
110
+ top,
111
+ base,
112
+ stackEnds: null,
113
+ slot: null
114
+ };
115
+ }
116
+ const y = yColumn(rows, input.props.y);
117
+ if (input.kind.waterfall) {
118
+ const steps = waterfallSteps(rows, y, input.props.total);
119
+ return {
120
+ input,
121
+ name,
122
+ rows,
123
+ axis: input.props.axis ?? chart.axis ?? "y",
124
+ x: column,
125
+ ...steps,
126
+ stackEnds: null,
127
+ slot: null
128
+ };
129
+ }
130
+ const y0 = input.props.y0;
131
+ return {
132
+ input,
133
+ name,
134
+ rows,
135
+ axis: input.props.axis ?? chart.axis ?? "y",
136
+ x: column,
137
+ y,
138
+ top: y,
139
+ base: y0 === void 0 ? null : yColumn(rows, y0),
140
+ stackEnds: null,
141
+ slot: null
142
+ };
143
+ });
144
+ let categories = [];
145
+ if (kind === "category") {
146
+ const columns = [...new Set(series.map((s) => s.x))];
147
+ if (columns.length === 1) categories = columns[0].categories;
148
+ else {
149
+ const at = /* @__PURE__ */ new Map();
150
+ for (const column of columns) for (const label of column.categories) if (!at.has(label)) at.set(label, at.size);
151
+ categories = [...at.keys()];
152
+ const remapped = /* @__PURE__ */ new Map();
153
+ for (const column of columns) {
154
+ const values = new Float64Array(column.values.length);
155
+ for (let i = 0; i < values.length; i++) values[i] = at.get(column.categories[column.values[i]]);
156
+ remapped.set(column, {
157
+ ...column,
158
+ values,
159
+ categories,
160
+ sorted: false
161
+ });
162
+ }
163
+ for (const s of series) s.x = remapped.get(s.x);
164
+ }
165
+ }
166
+ if (chart.sort) {
167
+ if (kind !== "category") warnOnce("sort", "an axis `sort` orders the categories of a category x axis; it does nothing here.");
168
+ else if (series.some((s) => s.input.kind.waterfall)) warnOnce("sort-waterfall", "a waterfall adds up in the order of its rows, so the x axis `sort` is left out.");
169
+ else if (categories.length > 1) categories = sortCategories(series, categories, chart.sort);
170
+ }
171
+ const yCategories = categoriesDown(series);
172
+ stack(series, chart.percent ?? {});
173
+ assignSlots(series);
174
+ let x = null;
175
+ let xStep = NaN;
176
+ if (kind === "category") x = categories.length ? [0, categories.length - 1] : null;
177
+ else for (const s of series) {
178
+ x = merge(x, s.x.sorted ? ends(s.x.values) : cachedExtent(s.x.values));
179
+ if (s.input.kind.band) {
180
+ const gap = s.input.kind.xStep?.(s) ?? smallestGap(s.x.values);
181
+ if (!(gap >= xStep)) xStep = gap;
182
+ }
183
+ }
184
+ const y = {
185
+ y: null,
186
+ y2: null
187
+ };
188
+ const zero = {
189
+ y: false,
190
+ y2: false
191
+ };
192
+ for (const s of series) {
193
+ if (yCategories[s.axis]) continue;
194
+ y[s.axis] = merge(y[s.axis], cachedExtent(s.top));
195
+ if (s.base) y[s.axis] = merge(y[s.axis], cachedExtent(s.base));
196
+ if (s.input.kind.zero && s.input.props.y0 === void 0) zero[s.axis] = true;
197
+ }
198
+ const xIntegers = kind === "linear" && series.every((s) => allIntegers(s.x.values));
199
+ let inset = 0;
200
+ for (const s of series) inset = Math.max(inset, s.input.kind.inset?.({
201
+ props: s.input.props,
202
+ rows: s.rows,
203
+ x: s.x,
204
+ y: s.y
205
+ }) ?? 0);
206
+ for (const axis of ["y", "y2"]) {
207
+ const list = yCategories[axis];
208
+ if (list) y[axis] = list.length ? [0, list.length - 1] : null;
209
+ }
210
+ return {
211
+ series,
212
+ xKind: kind,
213
+ categories,
214
+ x,
215
+ y,
216
+ zero,
217
+ yCategories,
218
+ xIntegers,
219
+ xStep,
220
+ inset
221
+ };
222
+ }
223
+ /** `categories` put in the order `sort` asks for, the series' x moved with them. */
224
+ function sortCategories(series, categories, sort) {
225
+ const order = categories.map((_, i) => i);
226
+ if (typeof sort === "function") order.sort((a, b) => sort(categories[a], categories[b]));
227
+ else {
228
+ const totals = new Float64Array(categories.length);
229
+ for (const s of series) {
230
+ if (s.input.kind.categoryY) continue;
231
+ for (let i = 0; i < s.y.length; i++) {
232
+ const x = s.x.values[i];
233
+ const v = s.y[i];
234
+ if (x === x && v === v) totals[x] = totals[x] + v;
235
+ }
236
+ }
237
+ const sign = sort === "desc" ? -1 : 1;
238
+ order.sort((a, b) => sign * (totals[a] - totals[b]) || a - b);
239
+ }
240
+ const place = new Float64Array(categories.length);
241
+ order.forEach((from, to) => place[from] = to);
242
+ const sorted = order.map((i) => categories[i]);
243
+ const moved = /* @__PURE__ */ new Map();
244
+ for (const s of series) {
245
+ let column = moved.get(s.x);
246
+ if (!column) {
247
+ const values = new Float64Array(s.x.values.length);
248
+ for (let i = 0; i < values.length; i++) {
249
+ const v = s.x.values[i];
250
+ values[i] = v === v ? place[v] : v;
251
+ }
252
+ column = {
253
+ ...s.x,
254
+ values,
255
+ categories: sorted,
256
+ sorted: false
257
+ };
258
+ moved.set(s.x, column);
259
+ }
260
+ s.x = column;
261
+ }
262
+ return sorted;
263
+ }
264
+ /**
265
+ * The categories down each value axis of series that list them
266
+ * (heatmaps), merged in order of first appearance; each series' `top`
267
+ * then gives its rows' places among them.
268
+ */
269
+ function categoriesDown(series) {
270
+ const out = {
271
+ y: null,
272
+ y2: null
273
+ };
274
+ for (const axis of ["y", "y2"]) {
275
+ const own = series.filter((s) => s.axis === axis && s.yColumn);
276
+ if (!own.length) continue;
277
+ const at = /* @__PURE__ */ new Map();
278
+ for (const s of own) for (const label of s.yColumn.categories) if (!at.has(label)) at.set(label, at.size);
279
+ for (const s of own) {
280
+ const { values, categories } = s.yColumn;
281
+ const top = new Float64Array(values.length);
282
+ for (let i = 0; i < values.length; i++) {
283
+ const v = values[i];
284
+ top[i] = v === v ? at.get(categories[v]) : NaN;
285
+ }
286
+ s.top = top;
287
+ }
288
+ out[axis] = [...at.keys()];
289
+ }
290
+ return out;
291
+ }
292
+ var gaps = /* @__PURE__ */ new WeakMap();
293
+ /** The smallest distance between two different values, or `NaN` with fewer than two. */
294
+ function smallestGap(values) {
295
+ let gap = gaps.get(values);
296
+ if (gap === void 0) {
297
+ const sorted = values.slice().sort();
298
+ gap = NaN;
299
+ for (let i = 1; i < sorted.length; i++) {
300
+ const d = sorted[i] - sorted[i - 1];
301
+ if (d > 0 && !(d >= gap)) gap = d;
302
+ }
303
+ gaps.set(values, gap);
304
+ }
305
+ return gap;
306
+ }
307
+ /**
308
+ * Gives each series that takes a band of each x (bars) its part of it: a
309
+ * stack shares one part, every other series gets its own.
310
+ */
311
+ function assignSlots(series) {
312
+ const keys = /* @__PURE__ */ new Map();
313
+ const banded = series.filter((s) => s.input.kind.band);
314
+ const keyOf = (s) => {
315
+ const name = s.input.props.stack;
316
+ return s.stackEnds && name !== void 0 && name !== false ? `${s.axis}\u0000${name === true ? "" : name}` : `#${s.input.index}`;
317
+ };
318
+ for (const s of banded) {
319
+ const key = keyOf(s);
320
+ if (!keys.has(key)) keys.set(key, keys.size);
321
+ }
322
+ for (const s of banded) s.slot = {
323
+ index: keys.get(keyOf(s)),
324
+ count: keys.size
325
+ };
326
+ }
327
+ /**
328
+ * Stacks series that share a `stack` name, the same axis and the same x
329
+ * values: each sits on the ones before it, positives upwards and
330
+ * negatives downwards. A gap counts as 0 for the ones above. On a
331
+ * `percent` axis, each stack is then scaled to reach 1 at every x: what
332
+ * it reaches up and down together.
333
+ */
334
+ function stack(series, percent) {
335
+ const groups = /* @__PURE__ */ new Map();
336
+ for (const s of series) {
337
+ const name = s.input.props.stack;
338
+ if (!s.input.kind.stackable || name === void 0 || name === false) continue;
339
+ if (s.input.props.y0 !== void 0) {
340
+ if (process.env.NODE_ENV !== "production") console.warn(`[@dou-ui/chart] series "${s.name}" has both \`y0\` and \`stack\`: a band can't stack, so it is drawn unstacked.`);
341
+ continue;
342
+ }
343
+ const key = `${s.axis}\u0000${name === true ? "" : name}`;
344
+ const group = groups.get(key);
345
+ if (!group) groups.set(key, [s]);
346
+ else if (group[0].x === s.x) group.push(s);
347
+ else if (process.env.NODE_ENV !== "production") console.warn(`[@dou-ui/chart] series "${s.name}" has other x values than its stack, so it is drawn unstacked.`);
348
+ }
349
+ for (const group of groups.values()) {
350
+ const length = group[0].y.length;
351
+ const up = new Float64Array(length);
352
+ const down = new Float64Array(length);
353
+ for (const s of group) {
354
+ const base = new Float64Array(length);
355
+ const top = new Float64Array(length);
356
+ for (let i = 0; i < length; i++) {
357
+ const v = s.y[i];
358
+ if (v !== v) {
359
+ base[i] = top[i] = NaN;
360
+ continue;
361
+ }
362
+ const from = v >= 0 ? up[i] : down[i];
363
+ base[i] = from;
364
+ top[i] = from + v;
365
+ if (v >= 0) up[i] = top[i];
366
+ else down[i] = top[i];
367
+ }
368
+ s.base = base;
369
+ s.top = top;
370
+ s.stackEnds = {
371
+ up,
372
+ down
373
+ };
374
+ }
375
+ if (!percent[group[0].axis]) continue;
376
+ for (let i = 0; i < length; i++) {
377
+ const total = up[i] - down[i];
378
+ if (!(total > 0)) continue;
379
+ for (const s of group) {
380
+ const base = s.base;
381
+ base[i] = base[i] / total;
382
+ s.top[i] = s.top[i] / total;
383
+ }
384
+ up[i] = up[i] / total;
385
+ down[i] = down[i] / total;
386
+ }
387
+ }
388
+ }
389
+ export { buildModel, seriesName, smallestGap };
390
+
391
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","names":[],"sources":["../../src/core/model.ts"],"sourcesContent":["import {\n extent,\n byIndex,\n inferXKind,\n rowCount,\n xColumn,\n yColumn,\n type ChartAccessor,\n type ChartData,\n type XColumn,\n type XKind,\n} from './data'\nimport type { ChartSeriesKind, ChartSeriesProps, ChartValueAxis } from './series'\nimport { waterfallSteps } from './waterfall'\nimport { warnOnce } from './warn'\nimport { boxesOf } from './box'\n\n/** A series as registered by its component. */\nexport interface SeriesInput {\n index: number\n kind: ChartSeriesKind<any>\n props: ChartSeriesProps & {\n stack?: string | boolean\n /** An area's bottom of its own: a band from it up to `y`. */\n y0?: ChartAccessor\n /** A heatmap's value of each cell (its `y` names the row's category). */\n value?: ChartAccessor\n } & Record<string, unknown>\n /** Turned off (in the legend): left out of the axes and not drawn. */\n hidden?: boolean\n}\n\n/** What a series is called: its `name`, else its `y` key, else its place. */\nexport function seriesName(input: Pick<SeriesInput, 'index' | 'props'>): string {\n return (\n input.props.name ?? (typeof input.props.y === 'string' ? input.props.y : `${input.index + 1}`)\n )\n}\n\n/** A series with its data read, before colors are worked out. */\nexport interface ModelSeries {\n input: SeriesInput\n name: string\n rows: ChartData\n axis: ChartValueAxis\n x: XColumn\n y: Float64Array\n top: Float64Array\n base: Float64Array | null\n stackEnds: { up: Float64Array; down: Float64Array } | null\n slot: { index: number; count: number } | null\n /** A heatmap's categories down the value axis, as read. */\n yColumn?: XColumn\n}\n\nexport interface ChartModel {\n series: ModelSeries[]\n xKind: XKind\n categories: string[]\n /** The smallest and largest x (for `category`: the first and last category index). */\n x: [number, number] | null\n /** The smallest and largest value on each y axis, with 0 when a series grows from it. */\n y: Record<ChartValueAxis, [number, number] | null>\n /** Whether each y axis must reach 0. */\n zero: Record<ChartValueAxis, boolean>\n /** The categories of value axes that list them (a heatmap's rows), first at the top; `null` for number axes. */\n yCategories: Record<ChartValueAxis, string[] | null>\n /** Whether every x is a whole number (years, counts): the x axis then has no ticks in between. */\n xIntegers: boolean\n /**\n * For series that take a band of each x (bars) on a number or time axis:\n * the smallest distance between two x values, which is one band. `NaN`\n * when there is no such series or no two values.\n */\n xStep: number\n /** The most room any series needs around its points, in px (bubbles). */\n inset: number\n}\n\nconst extents = new WeakMap<Float64Array, [number, number] | null>()\nfunction cachedExtent(values: Float64Array): [number, number] | null {\n if (!extents.has(values)) extents.set(values, extent(values))\n return extents.get(values)!\n}\n\n/** The first and last number of a sorted column: its extent, without going through it. */\nfunction ends(values: Float64Array): [number, number] | null {\n let a = 0\n let b = values.length - 1\n while (a <= b && values[a] !== values[a]) a++\n while (b >= a && values[b] !== values[b]) b--\n return a <= b ? [values[a]!, values[b]!] : null\n}\n\nconst wholes = new WeakMap<Float64Array, boolean>()\nfunction allIntegers(values: Float64Array): boolean {\n let known = wholes.get(values)\n if (known === undefined) {\n known = true\n for (let i = 0; i < values.length; i++) {\n const v = values[i]!\n if (v === v && !Number.isInteger(v)) {\n known = false\n break\n }\n }\n wholes.set(values, known)\n }\n return known\n}\n\nconst merge = (a: [number, number] | null, b: [number, number] | null): [number, number] | null =>\n !a ? b : !b ? a : [Math.min(a[0], b[0]), Math.max(a[1], b[1])]\n\n/**\n * Reads the data of every shown series into columns, lines up categories\n * shared by series with their own data, stacks what stacks, and finds the\n * range of each axis.\n */\nexport function buildModel(\n inputs: SeriesInput[],\n chart: {\n data: ChartData\n x?: ChartAccessor\n xKind?: XKind\n /** The y axis of series that don't name one. */\n axis?: ChartValueAxis\n /** Value axes whose stacks show each part's share of the whole (0 to 1). */\n percent?: Partial<Record<ChartValueAxis, boolean>>\n /**\n * The order of the categories along x: by their totals (the sum of the\n * shown series' values), or as a function of two category names orders\n * them. As they come in the data when not given.\n */\n sort?: ChartCategorySort\n },\n): ChartModel {\n const shown = inputs.filter((input) => !input.hidden)\n const sourceOf = (input: SeriesInput) => ({\n rows: input.props.data ?? chart.data,\n x: input.props.x ?? chart.x,\n })\n\n // how x is read: as the x axis says, else guessed from the first series with data\n let xKind = chart.xKind\n // a radar's spokes are its categories, whatever they are written as\n if (shown.some((input) => input.kind.polar)) xKind = 'category'\n if (!xKind) {\n xKind = 'linear'\n for (const input of shown) {\n const { rows, x } = sourceOf(input)\n if (x !== undefined && rowCount(rows)) {\n xKind = inferXKind(rows, x)\n break\n }\n }\n }\n const kind = xKind\n\n const series: ModelSeries[] = shown.map((input) => {\n const { rows, x } = sourceOf(input)\n // no x: rows are placed by their index\n const column = xColumn(rows, x ?? byIndex, x === undefined ? 'linear' : kind)\n const name = seriesName(input)\n if (input.kind.categoryY) {\n // a heatmap: `y` names a category, `value` is the cell's value\n const value = input.props.value\n const values = value === undefined ? new Float64Array(rowCount(rows)) : yColumn(rows, value)\n const places = xColumn(rows, input.props.y, 'category')\n return {\n input,\n name,\n rows,\n axis: input.props.axis ?? chart.axis ?? 'y',\n x: column,\n y: values,\n top: places.values,\n base: null,\n stackEnds: null,\n slot: null,\n yColumn: places,\n }\n }\n if (input.kind.box) {\n const list = boxesOf(rows, input.props.y, input.props.whisker as number | undefined)\n const n = list.length\n const [y, top, base] = [new Float64Array(n), new Float64Array(n), new Float64Array(n)]\n list.forEach((box, i) => {\n if (!box) {\n y[i] = top[i] = base[i] = Number.NaN\n return\n }\n y[i] = box.median\n base[i] = Math.min(box.low, ...box.outliers)\n top[i] = Math.max(box.high, ...box.outliers)\n })\n return {\n input,\n name,\n rows,\n axis: input.props.axis ?? chart.axis ?? 'y',\n x: column,\n y,\n top,\n base,\n stackEnds: null,\n slot: null,\n }\n }\n const y = yColumn(rows, input.props.y)\n if (input.kind.waterfall) {\n const steps = waterfallSteps(rows, y, input.props.total as ChartAccessor | undefined)\n return {\n input,\n name,\n rows,\n axis: input.props.axis ?? chart.axis ?? 'y',\n x: column,\n ...steps,\n stackEnds: null,\n slot: null,\n }\n }\n const y0 = input.props.y0\n return {\n input,\n name,\n rows,\n axis: input.props.axis ?? chart.axis ?? 'y',\n x: column,\n y,\n top: y,\n // a band: its bottom is read like its top\n base: y0 === undefined ? null : yColumn(rows, y0),\n stackEnds: null,\n slot: null,\n }\n })\n\n // categories of series with their own data are merged, in order of first appearance\n let categories: string[] = []\n if (kind === 'category') {\n const columns = [...new Set(series.map((s) => s.x))]\n if (columns.length === 1) categories = columns[0]!.categories\n else {\n const at = new Map<string, number>()\n for (const column of columns)\n for (const label of column.categories) if (!at.has(label)) at.set(label, at.size)\n categories = [...at.keys()]\n const remapped = new Map<XColumn, XColumn>()\n for (const column of columns) {\n const values = new Float64Array(column.values.length)\n for (let i = 0; i < values.length; i++)\n values[i] = at.get(column.categories[column.values[i]!]!)!\n remapped.set(column, { ...column, values, categories, sorted: false })\n }\n for (const s of series) s.x = remapped.get(s.x)!\n }\n }\n\n if (chart.sort) {\n // a waterfall adds up in the order of its rows: sorted, its totals would land anywhere\n if (kind !== 'category')\n warnOnce(\n 'sort',\n 'an axis `sort` orders the categories of a category x axis; it does nothing here.',\n )\n else if (series.some((s) => s.input.kind.waterfall))\n warnOnce(\n 'sort-waterfall',\n 'a waterfall adds up in the order of its rows, so the x axis `sort` is left out.',\n )\n else if (categories.length > 1) categories = sortCategories(series, categories, chart.sort)\n }\n\n const yCategories = categoriesDown(series)\n\n stack(series, chart.percent ?? {})\n assignSlots(series)\n\n let x: [number, number] | null = null\n let xStep = Number.NaN\n if (kind === 'category') x = categories.length ? [0, categories.length - 1] : null\n else {\n for (const s of series) {\n x = merge(x, s.x.sorted ? ends(s.x.values) : cachedExtent(s.x.values))\n if (s.input.kind.band) {\n const gap = s.input.kind.xStep?.(s as never) ?? smallestGap(s.x.values)\n if (!(gap >= xStep)) xStep = gap\n }\n }\n }\n\n const y: ChartModel['y'] = { y: null, y2: null }\n const zero: ChartModel['zero'] = { y: false, y2: false }\n for (const s of series) {\n if (yCategories[s.axis]) continue\n y[s.axis] = merge(y[s.axis], cachedExtent(s.top))\n if (s.base) y[s.axis] = merge(y[s.axis], cachedExtent(s.base))\n // a band floats where its values are; what grows from 0 makes the axis reach it\n if (s.input.kind.zero && s.input.props.y0 === undefined) zero[s.axis] = true\n }\n\n const xIntegers = kind === 'linear' && series.every((s) => allIntegers(s.x.values))\n let inset = 0\n for (const s of series)\n inset = Math.max(\n inset,\n s.input.kind.inset?.({ props: s.input.props, rows: s.rows, x: s.x, y: s.y }) ?? 0,\n )\n for (const axis of ['y', 'y2'] as const) {\n const list = yCategories[axis]\n if (list) y[axis] = list.length ? [0, list.length - 1] : null\n }\n return { series, xKind: kind, categories, x, y, zero, yCategories, xIntegers, xStep, inset }\n}\n\n/** How categories along x are ordered: by their totals, or by a comparison of their names. */\nexport type ChartCategorySort = 'asc' | 'desc' | ((a: string, b: string) => number)\n\n/** `categories` put in the order `sort` asks for, the series' x moved with them. */\nfunction sortCategories(\n series: ModelSeries[],\n categories: string[],\n sort: ChartCategorySort,\n): string[] {\n const order = categories.map((_, i) => i)\n if (typeof sort === 'function') order.sort((a, b) => sort(categories[a]!, categories[b]!))\n else {\n const totals = new Float64Array(categories.length)\n for (const s of series) {\n // a heatmap's values are cells, not amounts along x\n if (s.input.kind.categoryY) continue\n for (let i = 0; i < s.y.length; i++) {\n const x = s.x.values[i]!\n const v = s.y[i]!\n if (x === x && v === v) totals[x] = totals[x]! + v\n }\n }\n const sign = sort === 'desc' ? -1 : 1\n // ties keep the order they came in\n order.sort((a, b) => sign * (totals[a]! - totals[b]!) || a - b)\n }\n const place = new Float64Array(categories.length)\n order.forEach((from, to) => (place[from] = to))\n const sorted = order.map((i) => categories[i]!)\n const moved = new Map<XColumn, XColumn>()\n for (const s of series) {\n let column = moved.get(s.x)\n if (!column) {\n const values = new Float64Array(s.x.values.length)\n for (let i = 0; i < values.length; i++) {\n const v = s.x.values[i]!\n values[i] = v === v ? place[v]! : v\n }\n column = { ...s.x, values, categories: sorted, sorted: false }\n moved.set(s.x, column)\n }\n s.x = column\n }\n return sorted\n}\n\n/**\n * The categories down each value axis of series that list them\n * (heatmaps), merged in order of first appearance; each series' `top`\n * then gives its rows' places among them.\n */\nfunction categoriesDown(series: ModelSeries[]): ChartModel['yCategories'] {\n const out: ChartModel['yCategories'] = { y: null, y2: null }\n for (const axis of ['y', 'y2'] as const) {\n const own = series.filter((s) => s.axis === axis && s.yColumn)\n if (!own.length) continue\n const at = new Map<string, number>()\n for (const s of own)\n for (const label of s.yColumn!.categories) if (!at.has(label)) at.set(label, at.size)\n for (const s of own) {\n const { values, categories } = s.yColumn!\n const top = new Float64Array(values.length)\n for (let i = 0; i < values.length; i++) {\n const v = values[i]!\n top[i] = v === v ? at.get(categories[v]!)! : Number.NaN\n }\n s.top = top\n }\n out[axis] = [...at.keys()]\n }\n return out\n}\n\nconst gaps = new WeakMap<Float64Array, number>()\n/** The smallest distance between two different values, or `NaN` with fewer than two. */\nexport function smallestGap(values: Float64Array): number {\n let gap = gaps.get(values)\n if (gap === undefined) {\n // typed arrays sort as numbers, NaN last\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts a copy\n const sorted = values.slice().sort()\n gap = Number.NaN\n for (let i = 1; i < sorted.length; i++) {\n const d = sorted[i]! - sorted[i - 1]!\n if (d > 0 && !(d >= gap)) gap = d\n }\n gaps.set(values, gap)\n }\n return gap\n}\n\n/**\n * Gives each series that takes a band of each x (bars) its part of it: a\n * stack shares one part, every other series gets its own.\n */\nfunction assignSlots(series: ModelSeries[]) {\n const keys = new Map<string, number>()\n const banded = series.filter((s) => s.input.kind.band)\n const keyOf = (s: ModelSeries) => {\n const name = s.input.props.stack\n return s.stackEnds && name !== undefined && name !== false\n ? `${s.axis}\\u0000${name === true ? '' : name}`\n : `#${s.input.index}`\n }\n for (const s of banded) {\n const key = keyOf(s)\n if (!keys.has(key)) keys.set(key, keys.size)\n }\n for (const s of banded) s.slot = { index: keys.get(keyOf(s))!, count: keys.size }\n}\n\n/**\n * Stacks series that share a `stack` name, the same axis and the same x\n * values: each sits on the ones before it, positives upwards and\n * negatives downwards. A gap counts as 0 for the ones above. On a\n * `percent` axis, each stack is then scaled to reach 1 at every x: what\n * it reaches up and down together.\n */\nfunction stack(series: ModelSeries[], percent: Partial<Record<ChartValueAxis, boolean>>) {\n const groups = new Map<string, ModelSeries[]>()\n for (const s of series) {\n const name = s.input.props.stack\n if (!s.input.kind.stackable || name === undefined || name === false) continue\n if (s.input.props.y0 !== undefined) {\n if (process.env.NODE_ENV !== 'production')\n console.warn(\n `[@dou-ui/chart] series \"${s.name}\" has both \\`y0\\` and \\`stack\\`: a band can't stack, so it is drawn unstacked.`,\n )\n continue\n }\n const key = `${s.axis}\\u0000${name === true ? '' : name}`\n const group = groups.get(key)\n if (!group) groups.set(key, [s])\n else if (group[0]!.x === s.x) group.push(s)\n // the app's bundler replaces this, as for Vue's own warnings\n else if (process.env.NODE_ENV !== 'production')\n console.warn(\n `[@dou-ui/chart] series \"${s.name}\" has other x values than its stack, so it is drawn unstacked.`,\n )\n }\n for (const group of groups.values()) {\n const length = group[0]!.y.length\n const up = new Float64Array(length)\n const down = new Float64Array(length)\n for (const s of group) {\n const base = new Float64Array(length)\n const top = new Float64Array(length)\n for (let i = 0; i < length; i++) {\n const v = s.y[i]!\n if (v !== v) {\n // a gap: no slice of its own, the next one sits where it would\n base[i] = top[i] = Number.NaN\n continue\n }\n const from = v >= 0 ? up[i]! : down[i]!\n base[i] = from\n top[i] = from + v\n if (v >= 0) up[i] = top[i]!\n else down[i] = top[i]!\n }\n s.base = base\n s.top = top\n s.stackEnds = { up, down }\n }\n if (!percent[group[0]!.axis]) continue\n for (let i = 0; i < length; i++) {\n const total = up[i]! - down[i]!\n if (!(total > 0)) continue\n for (const s of group) {\n const base = s.base!\n base[i] = base[i]! / total\n s.top[i] = s.top[i]! / total\n }\n up[i] = up[i]! / total\n down[i] = down[i]! / total\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ // src/core/painter.ts
2
+ /**
3
+ * Moves a coordinate onto the pixel grid so a line `width` CSS px wide
4
+ * covers whole device pixels and stays sharp.
5
+ */
6
+ function crisp(value, dpr, width = 1) {
7
+ const offset = Math.round(width * dpr) % 2 === 1 ? .5 : 0;
8
+ return (Math.round(value * dpr - offset) + offset) / dpr;
9
+ }
10
+ /**
11
+ * Traces a line through the points `px[i], py[i]` into the current path.
12
+ * With `smooth`, a monotone curve: it bends through every point without
13
+ * swinging above or below a neighbor, so a peak in the curve is a peak in
14
+ * the data. `reverse` traces from the last point back to the first (the
15
+ * lower edge of an area); the curve is the same either way. With `swap`,
16
+ * `px` are tops and `py` lefts: the line runs down (a horizontal chart).
17
+ */
18
+ function tracePoints(painter, px, py, count, { smooth = false, reverse = false, join = false, swap = false } = {}) {
19
+ if (count === 0) return;
20
+ const at = (i) => reverse ? count - 1 - i : i;
21
+ const move = (a, b) => swap ? painter.moveTo(b, a) : painter.moveTo(a, b);
22
+ const line = (a, b) => swap ? painter.lineTo(b, a) : painter.lineTo(a, b);
23
+ const start = at(0);
24
+ if (join) line(px[start], py[start]);
25
+ else move(px[start], py[start]);
26
+ if (!smooth || count < 3) {
27
+ for (let k = 1; k < count; k++) line(px[at(k)], py[at(k)]);
28
+ return;
29
+ }
30
+ const tangents = monotoneTangents(px, py, count);
31
+ for (let k = 1; k < count; k++) {
32
+ const i = at(k - 1);
33
+ const j = at(k);
34
+ const dx = (px[j] - px[i]) / 3;
35
+ const c = [
36
+ px[i] + dx,
37
+ py[i] + dx * tangents[i],
38
+ px[j] - dx,
39
+ py[j] - dx * tangents[j]
40
+ ];
41
+ if (swap) painter.bezierCurveTo(c[1], c[0], c[3], c[2], py[j], px[j]);
42
+ else painter.bezierCurveTo(c[0], c[1], c[2], c[3], px[j], py[j]);
43
+ }
44
+ }
45
+ /** Slopes at each point that keep a cubic curve monotone between points (Fritsch–Carlson). */
46
+ function monotoneTangents(px, py, count) {
47
+ const slopes = new Float64Array(Math.max(0, count - 1));
48
+ for (let i = 0; i < count - 1; i++) {
49
+ const dx = px[i + 1] - px[i];
50
+ slopes[i] = dx === 0 ? 0 : (py[i + 1] - py[i]) / dx;
51
+ }
52
+ const tangents = new Float64Array(count);
53
+ tangents[0] = slopes[0] ?? 0;
54
+ tangents[count - 1] = slopes[count - 2] ?? 0;
55
+ for (let i = 1; i < count - 1; i++) {
56
+ const a = slopes[i - 1];
57
+ const b = slopes[i];
58
+ tangents[i] = a * b <= 0 ? 0 : (a + b) / 2;
59
+ }
60
+ for (let i = 0; i < count - 1; i++) {
61
+ const s = slopes[i];
62
+ if (s === 0) {
63
+ tangents[i] = tangents[i + 1] = 0;
64
+ continue;
65
+ }
66
+ const a = tangents[i] / s;
67
+ const b = tangents[i + 1] / s;
68
+ const h = a * a + b * b;
69
+ if (h > 9) {
70
+ const t = 3 / Math.sqrt(h);
71
+ tangents[i] = t * a * s;
72
+ tangents[i + 1] = t * b * s;
73
+ }
74
+ }
75
+ return tangents;
76
+ }
77
+ export { crisp, monotoneTangents, tracePoints };
78
+
79
+ //# sourceMappingURL=painter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"painter.js","names":[],"sources":["../../src/core/painter.ts"],"sourcesContent":["/**\n * What drawing code may use of a 2D context. Series draw only through\n * this, so the same code can later draw into something other than a\n * canvas (an SVG for export) and look the same.\n */\nexport type Painter = Pick<\n CanvasRenderingContext2D,\n | 'save'\n | 'restore'\n | 'beginPath'\n | 'closePath'\n | 'moveTo'\n | 'lineTo'\n | 'bezierCurveTo'\n | 'rect'\n | 'arc'\n | 'arcTo'\n | 'fill'\n | 'stroke'\n | 'clip'\n | 'setLineDash'\n | 'drawImage'\n | 'fillStyle'\n | 'strokeStyle'\n | 'lineWidth'\n | 'lineJoin'\n | 'lineCap'\n | 'globalAlpha'\n>\n\n/** A rectangle in CSS px. */\nexport interface Rect {\n x: number\n y: number\n width: number\n height: number\n}\n\n/**\n * Moves a coordinate onto the pixel grid so a line `width` CSS px wide\n * covers whole device pixels and stays sharp.\n */\nexport function crisp(value: number, dpr: number, width = 1): number {\n const device = Math.round(width * dpr)\n const offset = device % 2 === 1 ? 0.5 : 0\n return (Math.round(value * dpr - offset) + offset) / dpr\n}\n\n/**\n * Traces a line through the points `px[i], py[i]` into the current path.\n * With `smooth`, a monotone curve: it bends through every point without\n * swinging above or below a neighbor, so a peak in the curve is a peak in\n * the data. `reverse` traces from the last point back to the first (the\n * lower edge of an area); the curve is the same either way. With `swap`,\n * `px` are tops and `py` lefts: the line runs down (a horizontal chart).\n */\nexport function tracePoints(\n painter: Painter,\n px: ArrayLike<number>,\n py: ArrayLike<number>,\n count: number,\n { smooth = false, reverse = false, join = false, swap = false } = {},\n): void {\n if (count === 0) return\n const at = (i: number) => (reverse ? count - 1 - i : i)\n const move = (a: number, b: number) => (swap ? painter.moveTo(b, a) : painter.moveTo(a, b))\n const line = (a: number, b: number) => (swap ? painter.lineTo(b, a) : painter.lineTo(a, b))\n const start = at(0)\n if (join) line(px[start]!, py[start]!)\n else move(px[start]!, py[start]!)\n if (!smooth || count < 3) {\n for (let k = 1; k < count; k++) line(px[at(k)]!, py[at(k)]!)\n return\n }\n const tangents = monotoneTangents(px, py, count)\n for (let k = 1; k < count; k++) {\n const i = at(k - 1)\n const j = at(k)\n const dx = (px[j]! - px[i]!) / 3\n const c = [px[i]! + dx, py[i]! + dx * tangents[i]!, px[j]! - dx, py[j]! - dx * tangents[j]!]\n if (swap) painter.bezierCurveTo(c[1]!, c[0]!, c[3]!, c[2]!, py[j]!, px[j]!)\n else painter.bezierCurveTo(c[0]!, c[1]!, c[2]!, c[3]!, px[j]!, py[j]!)\n }\n}\n\n/** Slopes at each point that keep a cubic curve monotone between points (Fritsch–Carlson). */\nexport function monotoneTangents(\n px: ArrayLike<number>,\n py: ArrayLike<number>,\n count: number,\n): Float64Array {\n const slopes = new Float64Array(Math.max(0, count - 1))\n for (let i = 0; i < count - 1; i++) {\n const dx = px[i + 1]! - px[i]!\n slopes[i] = dx === 0 ? 0 : (py[i + 1]! - py[i]!) / dx\n }\n const tangents = new Float64Array(count)\n tangents[0] = slopes[0] ?? 0\n tangents[count - 1] = slopes[count - 2] ?? 0\n for (let i = 1; i < count - 1; i++) {\n const a = slopes[i - 1]!\n const b = slopes[i]!\n // a turn (or a flat side) keeps the point flat, so the curve can't overshoot it\n tangents[i] = a * b <= 0 ? 0 : (a + b) / 2\n }\n for (let i = 0; i < count - 1; i++) {\n const s = slopes[i]!\n if (s === 0) {\n tangents[i] = tangents[i + 1] = 0\n continue\n }\n const a = tangents[i]! / s\n const b = tangents[i + 1]! / s\n const h = a * a + b * b\n if (h > 9) {\n const t = 3 / Math.sqrt(h)\n tangents[i] = t * a * s\n tangents[i + 1] = t * b * s\n }\n }\n return tangents\n}\n"],"mappings":";;;;;AA0CA,SAAgB,MAAM,OAAe,KAAa,QAAQ,GAAW;CAEnE,MAAM,SADS,KAAK,MAAM,QAAQ,GACnB,IAAS,MAAM,IAAI,KAAM;CACxC,QAAQ,KAAK,MAAM,QAAQ,MAAM,MAAM,IAAI,UAAU;AACvD;;;;;;;;;AAUA,SAAgB,YACd,SACA,IACA,IACA,OACA,EAAE,SAAS,OAAO,UAAU,OAAO,OAAO,OAAO,OAAO,UAAU,CAAC,GAC7D;CACN,IAAI,UAAU,GAAG;CACjB,MAAM,MAAM,MAAe,UAAU,QAAQ,IAAI,IAAI;CACrD,MAAM,QAAQ,GAAW,MAAe,OAAO,QAAQ,OAAO,GAAG,CAAC,IAAI,QAAQ,OAAO,GAAG,CAAC;CACzF,MAAM,QAAQ,GAAW,MAAe,OAAO,QAAQ,OAAO,GAAG,CAAC,IAAI,QAAQ,OAAO,GAAG,CAAC;CACzF,MAAM,QAAQ,GAAG,CAAC;CAClB,IAAI,MAAM,KAAK,GAAG,QAAS,GAAG,MAAO;MAChC,KAAK,GAAG,QAAS,GAAG,MAAO;CAChC,IAAI,CAAC,UAAU,QAAQ,GAAG;EACxB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK,KAAK,GAAG,GAAG,CAAC,IAAK,GAAG,GAAG,CAAC,EAAG;EAC3D;CACF;CACA,MAAM,WAAW,iBAAiB,IAAI,IAAI,KAAK;CAC/C,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;EAC9B,MAAM,IAAI,GAAG,IAAI,CAAC;EAClB,MAAM,IAAI,GAAG,CAAC;EACd,MAAM,MAAM,GAAG,KAAM,GAAG,MAAO;EAC/B,MAAM,IAAI;GAAC,GAAG,KAAM;GAAI,GAAG,KAAM,KAAK,SAAS;GAAK,GAAG,KAAM;GAAI,GAAG,KAAM,KAAK,SAAS;EAAG;EAC3F,IAAI,MAAM,QAAQ,cAAc,EAAE,IAAK,EAAE,IAAK,EAAE,IAAK,EAAE,IAAK,GAAG,IAAK,GAAG,EAAG;OACrE,QAAQ,cAAc,EAAE,IAAK,EAAE,IAAK,EAAE,IAAK,EAAE,IAAK,GAAG,IAAK,GAAG,EAAG;CACvE;AACF;;AAGA,SAAgB,iBACd,IACA,IACA,OACc;CACd,MAAM,SAAS,IAAI,aAAa,KAAK,IAAI,GAAG,QAAQ,CAAC,CAAC;CACtD,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,GAAG,KAAK;EAClC,MAAM,KAAK,GAAG,IAAI,KAAM,GAAG;EAC3B,OAAO,KAAK,OAAO,IAAI,KAAK,GAAG,IAAI,KAAM,GAAG,MAAO;CACrD;CACA,MAAM,WAAW,IAAI,aAAa,KAAK;CACvC,SAAS,KAAK,OAAO,MAAM;CAC3B,SAAS,QAAQ,KAAK,OAAO,QAAQ,MAAM;CAC3C,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,GAAG,KAAK;EAClC,MAAM,IAAI,OAAO,IAAI;EACrB,MAAM,IAAI,OAAO;EAEjB,SAAS,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK;CAC3C;CACA,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,GAAG,KAAK;EAClC,MAAM,IAAI,OAAO;EACjB,IAAI,MAAM,GAAG;GACX,SAAS,KAAK,SAAS,IAAI,KAAK;GAChC;EACF;EACA,MAAM,IAAI,SAAS,KAAM;EACzB,MAAM,IAAI,SAAS,IAAI,KAAM;EAC7B,MAAM,IAAI,IAAI,IAAI,IAAI;EACtB,IAAI,IAAI,GAAG;GACT,MAAM,IAAI,IAAI,KAAK,KAAK,CAAC;GACzB,SAAS,KAAK,IAAI,IAAI;GACtB,SAAS,IAAI,KAAK,IAAI,IAAI;EAC5B;CACF;CACA,OAAO;AACT"}
@@ -0,0 +1,102 @@
1
+ // src/core/pattern.ts
2
+ /** The patterns in the order series and slices take them. */
3
+ var CHART_PATTERNS = [
4
+ "diagonal",
5
+ "dots",
6
+ "crosshatch",
7
+ "horizontal",
8
+ "backslash",
9
+ "grid",
10
+ "vertical"
11
+ ];
12
+ /** The room between lines (and dots), in px. */
13
+ var STEP = 6;
14
+ /**
15
+ * Draws `pattern` over `box` in `ink`, clipped to the current path: call
16
+ * it right after filling a shape, while its path is still the current one.
17
+ */
18
+ function fillPattern(painter, pattern, box, ink, alpha = .6) {
19
+ if (!(box.width > 0) || !(box.height > 0)) return;
20
+ painter.save();
21
+ painter.clip();
22
+ painter.globalAlpha *= alpha;
23
+ painter.strokeStyle = ink;
24
+ painter.fillStyle = ink;
25
+ painter.lineWidth = 1.25;
26
+ painter.lineCap = "butt";
27
+ painter.setLineDash([]);
28
+ const x0 = Math.floor(box.x / STEP) * STEP;
29
+ const y0 = Math.floor(box.y / STEP) * STEP;
30
+ const x1 = box.x + box.width;
31
+ const y1 = box.y + box.height;
32
+ painter.beginPath();
33
+ const lines = (dx, dy) => {
34
+ const span = x1 - x0 + (y1 - y0);
35
+ for (let k = -span; k <= span; k += STEP) if (dy === 0) {
36
+ painter.moveTo(x0, y0 + k + STEP / 2);
37
+ painter.lineTo(x1, y0 + k + STEP / 2);
38
+ } else if (dx === 0) {
39
+ painter.moveTo(x0 + k + STEP / 2, y0);
40
+ painter.lineTo(x0 + k + STEP / 2, y1);
41
+ } else {
42
+ painter.moveTo(x0 + k, y0);
43
+ painter.lineTo(x0 + k + dx / dy * (y1 - y0), y1);
44
+ }
45
+ };
46
+ switch (pattern) {
47
+ case "diagonal":
48
+ lines(-1, 1);
49
+ break;
50
+ case "backslash":
51
+ lines(1, 1);
52
+ break;
53
+ case "horizontal":
54
+ lines(1, 0);
55
+ break;
56
+ case "vertical":
57
+ lines(0, 1);
58
+ break;
59
+ case "crosshatch":
60
+ lines(-1, 1);
61
+ lines(1, 1);
62
+ break;
63
+ case "grid":
64
+ lines(1, 0);
65
+ lines(0, 1);
66
+ break;
67
+ case "dots": {
68
+ const r = 1.1;
69
+ for (let y = y0 + STEP / 2; y < y1 + STEP; y += STEP) for (let x = x0 + STEP / 2; x < x1 + STEP; x += STEP) {
70
+ const u = x + Math.round((y - y0) / STEP) % 2 * STEP / 2;
71
+ painter.moveTo(u + r, y);
72
+ painter.arc(u, y, r, 0, Math.PI * 2);
73
+ }
74
+ painter.fill();
75
+ painter.restore();
76
+ return;
77
+ }
78
+ }
79
+ painter.stroke();
80
+ painter.restore();
81
+ }
82
+ /** The same pattern for a legend swatch, as a CSS background image and its size. */
83
+ function patternCss(pattern, ink) {
84
+ return pattern === "dots" ? {
85
+ image: `radial-gradient(${ink} 0.75px, transparent 1px)`,
86
+ size: "3px 3px"
87
+ } : { image: linesCss(pattern, ink) };
88
+ }
89
+ function linesCss(pattern, ink) {
90
+ const line = (angle) => `repeating-linear-gradient(${angle}deg, ${ink} 0 1px, transparent 1px 3px)`;
91
+ switch (pattern) {
92
+ case "diagonal": return line(135);
93
+ case "backslash": return line(45);
94
+ case "horizontal": return line(0);
95
+ case "vertical": return line(90);
96
+ case "crosshatch": return `${line(45)}, ${line(-45)}`;
97
+ case "grid": return `${line(0)}, ${line(90)}`;
98
+ }
99
+ }
100
+ export { CHART_PATTERNS, fillPattern, patternCss };
101
+
102
+ //# sourceMappingURL=pattern.js.map