@dou-ui/chart 0.0.0-stage → 0.1.0-alpha.1

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,263 @@
1
+ import { nearestPoint } from "./hit.js";
2
+ import { yColumn } from "./data.js";
3
+ import { eachNear, pointGrid } from "./grid.js";
4
+ // src/core/scatter.ts
5
+ /** The radius of the largest bubble, unless set. */
6
+ var BUBBLE = 24;
7
+ /** The smallest bubble radius, so small values still show. */
8
+ var MIN_BUBBLE = 2;
9
+ /** From this many dots on, they are stamped into one image instead of drawn one by one. */
10
+ var MANY = 2e3;
11
+ var orders = /* @__PURE__ */ new WeakMap();
12
+ /** Indices from the largest size to the smallest, so small bubbles stay on top. */
13
+ function largestFirst(sizes) {
14
+ let order = orders.get(sizes);
15
+ if (!order) {
16
+ order = Int32Array.from(sizes, (_, i) => i);
17
+ order.sort((a, b) => (sizes[b] || 0) - (sizes[a] || 0));
18
+ orders.set(sizes, order);
19
+ }
20
+ return order;
21
+ }
22
+ /**
23
+ * Draws a dot at each point; with `size`, a bubble whose area is its size.
24
+ * Dots are a little see-through so dots on top of each other show darker;
25
+ * bubbles are lighter, with an edge, so they read through each other.
26
+ * From a few thousand dots on, they are stamped into one image: quicker
27
+ * by far, though a pile of dots is then no darker than one.
28
+ */
29
+ /** Where row `i` is drawn; `null` when it has no value or is well outside the plot. */
30
+ function pointOf(series, { x: sx, y: sy, plot, horizontal }) {
31
+ const xs = series.x.values;
32
+ const ys = series.top;
33
+ return (i) => {
34
+ const x = xs[i];
35
+ const y = ys[i];
36
+ if (x !== x || y !== y) return null;
37
+ const u = sx.map(x);
38
+ const v = sy.map(y);
39
+ const [px, py] = horizontal ? [v, u] : [u, v];
40
+ if (px < plot.x - 8 || px > plot.x + plot.width + 8) return null;
41
+ if (py < plot.y - 8 || py > plot.y + plot.height + 8) return null;
42
+ return [px, py];
43
+ };
44
+ }
45
+ /** The radius of a bubble of each size: its area grows with the size. */
46
+ function bubbleRadius(sizes, max = BUBBLE) {
47
+ let largest = 0;
48
+ for (let i = 0; i < sizes.length; i++) if (sizes[i] > largest) largest = sizes[i];
49
+ return (size) => largest > 0 ? Math.max(MIN_BUBBLE, max * Math.sqrt(size / largest)) : MIN_BUBBLE;
50
+ }
51
+ var DOT = 3.5;
52
+ function drawScatter(painter, series, view) {
53
+ const { plot } = view;
54
+ const xs = series.x.values;
55
+ const ys = series.top;
56
+ const sizeKey = series.props.size;
57
+ const sizes = sizeKey === void 0 ? null : yColumn(series.rows, sizeKey);
58
+ const n = Math.min(xs.length, ys.length);
59
+ const point = pointOf(series, view);
60
+ painter.save();
61
+ painter.fillStyle = series.color;
62
+ painter.strokeStyle = series.color;
63
+ if (sizes) {
64
+ const radius = bubbleRadius(sizes, series.props.radius);
65
+ painter.lineWidth = 1;
66
+ for (const i of largestFirst(sizes)) {
67
+ if (i >= n) continue;
68
+ const size = sizes[i];
69
+ if (!(size >= 0)) continue;
70
+ const at = point(i);
71
+ if (!at) continue;
72
+ const r = radius(size);
73
+ const own = series.colors?.[i];
74
+ painter.fillStyle = own ?? series.color;
75
+ painter.strokeStyle = own ?? series.color;
76
+ painter.beginPath();
77
+ painter.moveTo(at[0] + r, at[1]);
78
+ painter.arc(at[0], at[1], r, 0, Math.PI * 2);
79
+ painter.globalAlpha = .36;
80
+ painter.fill();
81
+ painter.globalAlpha = .9;
82
+ painter.stroke();
83
+ }
84
+ painter.restore();
85
+ return;
86
+ }
87
+ const r = series.props.radius ?? DOT;
88
+ painter.globalAlpha = .72;
89
+ if (n < MANY) for (let i = 0; i < n; i++) {
90
+ const at = point(i);
91
+ if (!at) continue;
92
+ if (series.colors) painter.fillStyle = series.colors[i] ?? series.color;
93
+ painter.beginPath();
94
+ painter.arc(at[0], at[1], r, 0, Math.PI * 2);
95
+ painter.fill();
96
+ }
97
+ else if (view.vector || !stampDots(painter, series.color, view, r, n, point)) {
98
+ const columns = Math.ceil(plot.width) + 17;
99
+ const seen = take(columns * (Math.ceil(plot.height) + 17));
100
+ painter.beginPath();
101
+ for (let i = 0; i < n; i++) {
102
+ const at = point(i);
103
+ if (!at) continue;
104
+ const cell = Math.round(at[1] - plot.y + 8) * columns + Math.round(at[0] - plot.x + 8);
105
+ if (seen[cell]) continue;
106
+ seen[cell] = 1;
107
+ painter.moveTo(at[0] + r, at[1]);
108
+ painter.arc(at[0], at[1], r, 0, Math.PI * 2);
109
+ }
110
+ painter.fill();
111
+ }
112
+ painter.restore();
113
+ }
114
+ var buffer = /* @__PURE__ */ new Uint8Array(0);
115
+ /** A zeroed buffer of at least `length` bytes, reused between draws. */
116
+ function take(length) {
117
+ if (buffer.length < length) buffer = new Uint8Array(length);
118
+ else buffer.fill(0, 0, length);
119
+ return buffer;
120
+ }
121
+ /** How much of each pixel a dot of radius `radius` device px covers, around a center pixel. */
122
+ var masks = /* @__PURE__ */ new Map();
123
+ function dotMask(radius) {
124
+ const key = Math.round(radius * 4) / 4;
125
+ let mask = masks.get(key);
126
+ if (!mask) {
127
+ const half = Math.ceil(key + .5);
128
+ const size = half * 2 + 1;
129
+ const cover = new Uint8Array(size * size);
130
+ for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
131
+ const d = Math.hypot(x - half, y - half);
132
+ cover[y * size + x] = Math.round(Math.min(1, Math.max(0, key + .5 - d)) * 255);
133
+ }
134
+ masks.set(key, mask = {
135
+ half,
136
+ size,
137
+ cover
138
+ });
139
+ }
140
+ return mask;
141
+ }
142
+ var layer = null;
143
+ /**
144
+ * Draws many dots by stamping them into one image: a dot is a small mask
145
+ * of how much of each pixel it covers, the image keeps the most any dot
146
+ * covers, and the result is drawn once in the series' color. It looks the
147
+ * same as drawing every dot as a shape and is many times quicker. Returns
148
+ * `false` when there is no canvas to stamp on.
149
+ */
150
+ function stampDots(painter, color, { plot, dpr }, r, n, point) {
151
+ if (typeof document === "undefined") return false;
152
+ layer ??= document.createElement("canvas");
153
+ const ctx = layer.getContext("2d");
154
+ if (!ctx) return false;
155
+ const room = Math.ceil(r) + 8;
156
+ const left = Math.floor((plot.x - room) * dpr) / dpr;
157
+ const top = Math.floor((plot.y - room) * dpr) / dpr;
158
+ const width = Math.ceil((plot.width + room * 2) * dpr);
159
+ const height = Math.ceil((plot.height + room * 2) * dpr);
160
+ const { half, size, cover } = dotMask(r * dpr);
161
+ const alpha = take(width * height * 2);
162
+ const centers = alpha.subarray(width * height);
163
+ for (let i = 0; i < n; i++) {
164
+ const at = point(i);
165
+ if (!at) continue;
166
+ const cx = Math.round((at[0] - left) * dpr);
167
+ const cy = Math.round((at[1] - top) * dpr);
168
+ if (cx < 0 || cy < 0 || cx >= width || cy >= height) continue;
169
+ const center = cy * width + cx;
170
+ if (centers[center]) continue;
171
+ centers[center] = 1;
172
+ const x0 = Math.max(0, half - cx);
173
+ const x1 = Math.min(size, width - cx + half);
174
+ const y0 = Math.max(0, half - cy);
175
+ const y1 = Math.min(size, height - cy + half);
176
+ for (let y = y0; y < y1; y++) {
177
+ const row = (cy - half + y) * width + cx - half;
178
+ const maskRow = y * size;
179
+ for (let x = x0; x < x1; x++) {
180
+ const c = cover[maskRow + x];
181
+ if (c > alpha[row + x]) alpha[row + x] = c;
182
+ }
183
+ }
184
+ }
185
+ if (layer.width !== width) layer.width = width;
186
+ if (layer.height !== height) layer.height = height;
187
+ const image = ctx.createImageData(width, height);
188
+ const pixels = image.data;
189
+ for (let i = 0, j = 3; i < width * height; i++, j += 4) pixels[j] = alpha[i];
190
+ ctx.putImageData(image, 0, 0);
191
+ ctx.globalCompositeOperation = "source-in";
192
+ ctx.fillStyle = color;
193
+ ctx.fillRect(0, 0, width, height);
194
+ ctx.globalCompositeOperation = "source-over";
195
+ painter.drawImage(layer, left, top, width / dpr, height / dpr);
196
+ return true;
197
+ }
198
+ /**
199
+ * The point under `(px, py)`: the bubble it is inside (the smallest, which
200
+ * is drawn on top), or the dot nearest to it within a finger's reach.
201
+ */
202
+ function hitScatter(series, view, px, py) {
203
+ const point = pointOf(series, view);
204
+ const n = Math.min(series.x.values.length, series.top.length);
205
+ const sizeKey = series.props.size;
206
+ if (sizeKey === void 0) {
207
+ const r = series.props.radius ?? DOT;
208
+ const reach = Math.max(r + 4, 12);
209
+ if (n < MANY) {
210
+ const hit = nearestPoint(n, point, px, py, () => reach);
211
+ return hit && {
212
+ index: hit.index,
213
+ radius: r
214
+ };
215
+ }
216
+ const { x: sx, y: sy, horizontal } = view;
217
+ const [along, across] = horizontal ? [py, px] : [px, py];
218
+ let index = -1;
219
+ let distance = Infinity;
220
+ eachNear(pointGrid(series.x.values, series.top), sx.invert(along - reach), sx.invert(along + reach), sy.invert(across - reach), sy.invert(across + reach), (i) => {
221
+ const at = point(i);
222
+ if (!at) return;
223
+ const d = Math.hypot(at[0] - px, at[1] - py);
224
+ if (d <= reach && (d < distance || d === distance && i > index)) {
225
+ index = i;
226
+ distance = d;
227
+ }
228
+ });
229
+ return index < 0 ? null : {
230
+ index,
231
+ radius: r
232
+ };
233
+ }
234
+ const sizes = yColumn(series.rows, sizeKey);
235
+ const radius = bubbleRadius(sizes, series.props.radius);
236
+ let best = null;
237
+ for (let i = 0; i < n; i++) {
238
+ const size = sizes[i];
239
+ if (!(size >= 0)) continue;
240
+ const at = point(i);
241
+ if (!at) continue;
242
+ const r = radius(size);
243
+ if (Math.hypot(at[0] - px, at[1] - py) > Math.max(r, 8)) continue;
244
+ if (!best || r < best.radius) best = {
245
+ index: i,
246
+ radius: r
247
+ };
248
+ }
249
+ return best;
250
+ }
251
+ var scatterKind = {
252
+ type: "scatter",
253
+ tooltip: "item",
254
+ hit: hitScatter,
255
+ prepare: (series) => {
256
+ if (Math.min(series.x.values.length, series.top.length) >= MANY && series.props.size === void 0) return pointGrid(series.x.values, series.top);
257
+ },
258
+ inset: ({ props }) => props.size === void 0 ? 0 : (props.radius ?? BUBBLE) + 1,
259
+ draw: drawScatter
260
+ };
261
+ export { drawScatter, hitScatter, scatterKind };
262
+
263
+ //# sourceMappingURL=scatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scatter.js","names":[],"sources":["../../src/core/scatter.ts"],"sourcesContent":["import { yColumn, type ChartAccessor } from './data'\nimport type { Painter } from './painter'\nimport { eachNear, pointGrid } from './grid'\nimport { nearestPoint } from './hit'\nimport type { ChartHit, ChartSeries, ChartSeriesKind, ChartView } from './series'\n\nexport interface ScatterStyle {\n /** The bubble size of each row; the area of a bubble grows with it. */\n size?: ChartAccessor\n /** Dot radius in px; with `size`, the radius of the largest bubble. */\n radius?: number\n}\n\n/** The radius of the largest bubble, unless set. */\nconst BUBBLE = 24\n/** The smallest bubble radius, so small values still show. */\nconst MIN_BUBBLE = 2\n/** From this many dots on, they are stamped into one image instead of drawn one by one. */\nconst MANY = 2000\n\nconst orders = new WeakMap<Float64Array, Int32Array>()\n/** Indices from the largest size to the smallest, so small bubbles stay on top. */\nfunction largestFirst(sizes: Float64Array): Int32Array {\n let order = orders.get(sizes)\n if (!order) {\n order = Int32Array.from(sizes, (_, i) => i)\n // the `|| 0` keeps NaN (no size) at the end\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts its own array\n order.sort((a, b) => (sizes[b]! || 0) - (sizes[a]! || 0))\n orders.set(sizes, order)\n }\n return order\n}\n\n/**\n * Draws a dot at each point; with `size`, a bubble whose area is its size.\n * Dots are a little see-through so dots on top of each other show darker;\n * bubbles are lighter, with an edge, so they read through each other.\n * From a few thousand dots on, they are stamped into one image: quicker\n * by far, though a pile of dots is then no darker than one.\n */\n/** Where row `i` is drawn; `null` when it has no value or is well outside the plot. */\nfunction pointOf(series: ChartSeries<ScatterStyle>, { x: sx, y: sy, plot, horizontal }: ChartView) {\n const xs = series.x.values\n const ys = series.top\n return (i: number): [number, number] | null => {\n const x = xs[i]!\n const y = ys[i]!\n if (x !== x || y !== y) return null\n const u = sx.map(x)\n const v = sy.map(y)\n const [px, py] = horizontal ? [v, u] : [u, v]\n // a little room outside the plot so dots on the edge show whole\n if (px < plot.x - 8 || px > plot.x + plot.width + 8) return null\n if (py < plot.y - 8 || py > plot.y + plot.height + 8) return null\n return [px, py]\n }\n}\n\n/** The radius of a bubble of each size: its area grows with the size. */\nfunction bubbleRadius(sizes: Float64Array, max = BUBBLE) {\n let largest = 0\n for (let i = 0; i < sizes.length; i++) if (sizes[i]! > largest) largest = sizes[i]!\n return (size: number) =>\n largest > 0 ? Math.max(MIN_BUBBLE, max * Math.sqrt(size / largest)) : MIN_BUBBLE\n}\n\nconst DOT = 3.5\n\nexport function drawScatter(painter: Painter, series: ChartSeries<ScatterStyle>, view: ChartView) {\n const { plot } = view\n const xs = series.x.values\n const ys = series.top\n const sizeKey = series.props.size\n const sizes = sizeKey === undefined ? null : yColumn(series.rows, sizeKey)\n const n = Math.min(xs.length, ys.length)\n\n const point = pointOf(series, view)\n\n painter.save()\n painter.fillStyle = series.color\n painter.strokeStyle = series.color\n\n if (sizes) {\n const radius = bubbleRadius(sizes, series.props.radius)\n painter.lineWidth = 1\n // one at a time: each bubble's edge goes over the fill of those under it\n for (const i of largestFirst(sizes)) {\n if (i >= n) continue\n const size = sizes[i]!\n if (!(size >= 0)) continue\n const at = point(i)\n if (!at) continue\n const r = radius(size)\n const own = series.colors?.[i]\n painter.fillStyle = own ?? series.color\n painter.strokeStyle = own ?? series.color\n painter.beginPath()\n painter.moveTo(at[0] + r, at[1])\n painter.arc(at[0], at[1], r, 0, Math.PI * 2)\n painter.globalAlpha = 0.36\n painter.fill()\n painter.globalAlpha = 0.9\n painter.stroke()\n }\n painter.restore()\n return\n }\n\n const r = series.props.radius ?? DOT\n painter.globalAlpha = 0.72\n if (n < MANY) {\n for (let i = 0; i < n; i++) {\n const at = point(i)\n if (!at) continue\n if (series.colors) painter.fillStyle = series.colors[i] ?? series.color\n painter.beginPath()\n painter.arc(at[0], at[1], r, 0, Math.PI * 2)\n painter.fill()\n }\n } else if (view.vector || !stampDots(painter, series.color, view, r, n, point)) {\n // no canvas to stamp on (an SVG, or not in a page): one shape, a dot per pixel\n const columns = Math.ceil(plot.width) + 17\n const cells = columns * (Math.ceil(plot.height) + 17)\n const seen = take(cells)\n painter.beginPath()\n for (let i = 0; i < n; i++) {\n const at = point(i)\n if (!at) continue\n const cell = Math.round(at[1] - plot.y + 8) * columns + Math.round(at[0] - plot.x + 8)\n if (seen[cell]) continue\n seen[cell] = 1\n painter.moveTo(at[0] + r, at[1])\n painter.arc(at[0], at[1], r, 0, Math.PI * 2)\n }\n painter.fill()\n }\n painter.restore()\n}\n\nlet buffer = new Uint8Array(0)\n/** A zeroed buffer of at least `length` bytes, reused between draws. */\nfunction take(length: number): Uint8Array {\n if (buffer.length < length) buffer = new Uint8Array(length)\n else buffer.fill(0, 0, length)\n return buffer\n}\n\n/** How much of each pixel a dot of radius `radius` device px covers, around a center pixel. */\nconst masks = new Map<number, { half: number; size: number; cover: Uint8Array }>()\nfunction dotMask(radius: number) {\n const key = Math.round(radius * 4) / 4\n let mask = masks.get(key)\n if (!mask) {\n const half = Math.ceil(key + 0.5)\n const size = half * 2 + 1\n const cover = new Uint8Array(size * size)\n for (let y = 0; y < size; y++)\n for (let x = 0; x < size; x++) {\n const d = Math.hypot(x - half, y - half)\n cover[y * size + x] = Math.round(Math.min(1, Math.max(0, key + 0.5 - d)) * 255)\n }\n masks.set(key, (mask = { half, size, cover }))\n }\n return mask\n}\n\nlet layer: HTMLCanvasElement | null = null\n\n/**\n * Draws many dots by stamping them into one image: a dot is a small mask\n * of how much of each pixel it covers, the image keeps the most any dot\n * covers, and the result is drawn once in the series' color. It looks the\n * same as drawing every dot as a shape and is many times quicker. Returns\n * `false` when there is no canvas to stamp on.\n */\nfunction stampDots(\n painter: Painter,\n color: string,\n { plot, dpr }: ChartView,\n r: number,\n n: number,\n point: (i: number) => [number, number] | null,\n): boolean {\n if (typeof document === 'undefined') return false\n layer ??= document.createElement('canvas')\n const ctx = layer.getContext('2d')\n if (!ctx) return false\n const room = Math.ceil(r) + 8\n // on whole device pixels, so the image lands on the canvas unscaled\n const left = Math.floor((plot.x - room) * dpr) / dpr\n const top = Math.floor((plot.y - room) * dpr) / dpr\n const width = Math.ceil((plot.width + room * 2) * dpr)\n const height = Math.ceil((plot.height + room * 2) * dpr)\n const { half, size, cover } = dotMask(r * dpr)\n const alpha = take(width * height * 2)\n const centers = alpha.subarray(width * height)\n\n for (let i = 0; i < n; i++) {\n const at = point(i)\n if (!at) continue\n const cx = Math.round((at[0] - left) * dpr)\n const cy = Math.round((at[1] - top) * dpr)\n if (cx < 0 || cy < 0 || cx >= width || cy >= height) continue\n // a dot already stamped here: nothing to add\n const center = cy * width + cx\n if (centers[center]) continue\n centers[center] = 1\n const x0 = Math.max(0, half - cx)\n const x1 = Math.min(size, width - cx + half)\n const y0 = Math.max(0, half - cy)\n const y1 = Math.min(size, height - cy + half)\n for (let y = y0; y < y1; y++) {\n const row = (cy - half + y) * width + cx - half\n const maskRow = y * size\n for (let x = x0; x < x1; x++) {\n const c = cover[maskRow + x]!\n if (c > alpha[row + x]!) alpha[row + x] = c\n }\n }\n }\n\n if (layer.width !== width) layer.width = width\n if (layer.height !== height) layer.height = height\n const image = ctx.createImageData(width, height)\n const pixels = image.data\n for (let i = 0, j = 3; i < width * height; i++, j += 4) pixels[j] = alpha[i]!\n ctx.putImageData(image, 0, 0)\n // color what was stamped\n ctx.globalCompositeOperation = 'source-in'\n ctx.fillStyle = color\n ctx.fillRect(0, 0, width, height)\n ctx.globalCompositeOperation = 'source-over'\n painter.drawImage(layer, left, top, width / dpr, height / dpr)\n return true\n}\n\n/**\n * The point under `(px, py)`: the bubble it is inside (the smallest, which\n * is drawn on top), or the dot nearest to it within a finger's reach.\n */\nexport function hitScatter(\n series: ChartSeries<ScatterStyle>,\n view: ChartView,\n px: number,\n py: number,\n): ChartHit | null {\n const point = pointOf(series, view)\n const n = Math.min(series.x.values.length, series.top.length)\n const sizeKey = series.props.size\n if (sizeKey === undefined) {\n const r = series.props.radius ?? DOT\n const reach = Math.max(r + 4, 12)\n if (n < MANY) {\n const hit = nearestPoint(n, point, px, py, () => reach)\n return hit && { index: hit.index, radius: r }\n }\n // many dots: only those in the cells around the pointer\n const { x: sx, y: sy, horizontal } = view\n const [along, across] = horizontal ? [py, px] : [px, py]\n let index = -1\n let distance = Infinity\n eachNear(\n pointGrid(series.x.values, series.top),\n sx.invert(along - reach),\n sx.invert(along + reach),\n sy.invert(across - reach),\n sy.invert(across + reach),\n (i) => {\n const at = point(i)\n if (!at) return\n const d = Math.hypot(at[0] - px, at[1] - py)\n // as with fewer dots: of equal ones, the one drawn last\n if (d <= reach && (d < distance || (d === distance && i > index))) {\n index = i\n distance = d\n }\n },\n )\n return index < 0 ? null : { index, radius: r }\n }\n const sizes = yColumn(series.rows, sizeKey)\n const radius = bubbleRadius(sizes, series.props.radius)\n let best: ChartHit | null = null\n for (let i = 0; i < n; i++) {\n const size = sizes[i]!\n if (!(size >= 0)) continue\n const at = point(i)\n if (!at) continue\n const r = radius(size)\n // small bubbles get a little more room, so they can be reached\n if (Math.hypot(at[0] - px, at[1] - py) > Math.max(r, 8)) continue\n if (!best || r < best.radius) best = { index: i, radius: r }\n }\n return best\n}\n\nexport const scatterKind: ChartSeriesKind<ScatterStyle> = {\n type: 'scatter',\n tooltip: 'item',\n hit: hitScatter,\n // the grid for finding dots under the pointer, when there are many\n prepare: (series) => {\n const n = Math.min(series.x.values.length, series.top.length)\n if (n >= MANY && series.props.size === undefined) return pointGrid(series.x.values, series.top)\n },\n // bubbles may be large: keep them inside the plot\n inset: ({ props }) => (props.size === undefined ? 0 : (props.radius ?? BUBBLE) + 1),\n draw: drawScatter,\n}\n"],"mappings":"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"}
@@ -0,0 +1,209 @@
1
+ // src/core/svg.ts
2
+ var TAU = Math.PI * 2;
3
+ /** A number with at most two decimals, short in the markup. */
4
+ var num = (value) => String(Math.round(value * 100) / 100);
5
+ var ESCAPES = {
6
+ "&": "&amp;",
7
+ "<": "&lt;",
8
+ ">": "&gt;",
9
+ "\"": "&quot;",
10
+ "'": "&#39;"
11
+ };
12
+ /** Text made safe to put in markup, as content or in an attribute. */
13
+ var escapeXml = (text) => text.replace(/[&<>"']/g, (c) => ESCAPES[c]);
14
+ /** `name="value"` for a color, or a style when SVG attributes can't take it (`var(…)`, `color-mix(…)`). */
15
+ function paintAttribute(name, color) {
16
+ return /^(var|color-mix)\(/.test(color) ? ` style="${name}:${escapeXml(color)}"` : ` ${name}="${escapeXml(color)}"`;
17
+ }
18
+ var SvgPainter = class {
19
+ prefix;
20
+ color;
21
+ fillStyle = "#000";
22
+ strokeStyle = "#000";
23
+ lineWidth = 1;
24
+ lineJoin = "miter";
25
+ lineCap = "butt";
26
+ globalAlpha = 1;
27
+ dash = [];
28
+ clipId = null;
29
+ states = [];
30
+ path = "";
31
+ /** The current point, and where the current subpath starts; `NaN` when there is none. */
32
+ cx = NaN;
33
+ cy = NaN;
34
+ sx = NaN;
35
+ sy = NaN;
36
+ body = [];
37
+ defs = [];
38
+ clips = 0;
39
+ ids = 0;
40
+ /**
41
+ * `prefix` starts every id the markup gives, so two SVGs in one page
42
+ * don't share them; `color` turns a color into one the SVG's reader
43
+ * understands.
44
+ */
45
+ constructor(prefix, color = (css) => css) {
46
+ this.prefix = prefix;
47
+ this.color = color;
48
+ }
49
+ /** An id of its own in this SVG, for something drawn as raw markup (a gradient). */
50
+ id(name) {
51
+ return `${this.prefix}${name}${this.ids++}`;
52
+ }
53
+ save() {
54
+ this.states.push({
55
+ fillStyle: String(this.fillStyle),
56
+ strokeStyle: String(this.strokeStyle),
57
+ lineWidth: this.lineWidth,
58
+ lineJoin: this.lineJoin,
59
+ lineCap: this.lineCap,
60
+ globalAlpha: this.globalAlpha,
61
+ dash: this.dash,
62
+ clip: this.clipId
63
+ });
64
+ }
65
+ restore() {
66
+ const state = this.states.pop();
67
+ if (!state) return;
68
+ this.fillStyle = state.fillStyle;
69
+ this.strokeStyle = state.strokeStyle;
70
+ this.lineWidth = state.lineWidth;
71
+ this.lineJoin = state.lineJoin;
72
+ this.lineCap = state.lineCap;
73
+ this.globalAlpha = state.globalAlpha;
74
+ this.dash = state.dash;
75
+ this.clipId = state.clip;
76
+ }
77
+ beginPath() {
78
+ this.path = "";
79
+ this.cx = this.cy = this.sx = this.sy = NaN;
80
+ }
81
+ closePath() {
82
+ if (!this.path) return;
83
+ this.path += "Z";
84
+ this.cx = this.sx;
85
+ this.cy = this.sy;
86
+ }
87
+ moveTo(x, y) {
88
+ this.path += `M${num(x)} ${num(y)}`;
89
+ this.cx = this.sx = x;
90
+ this.cy = this.sy = y;
91
+ }
92
+ lineTo(x, y) {
93
+ if (this.cx !== this.cx) return this.moveTo(x, y);
94
+ this.path += `L${num(x)} ${num(y)}`;
95
+ this.cx = x;
96
+ this.cy = y;
97
+ }
98
+ bezierCurveTo(c1x, c1y, c2x, c2y, x, y) {
99
+ if (this.cx !== this.cx) this.moveTo(c1x, c1y);
100
+ this.path += `C${num(c1x)} ${num(c1y)} ${num(c2x)} ${num(c2y)} ${num(x)} ${num(y)}`;
101
+ this.cx = x;
102
+ this.cy = y;
103
+ }
104
+ rect(x, y, width, height) {
105
+ this.path += `M${num(x)} ${num(y)}h${num(width)}v${num(height)}h${num(-width)}Z`;
106
+ this.cx = this.sx = x;
107
+ this.cy = this.sy = y;
108
+ }
109
+ arc(x, y, radius, startAngle, endAngle, counterclockwise = false) {
110
+ const r = Math.abs(radius);
111
+ const start = [x + r * Math.cos(startAngle), y + r * Math.sin(startAngle)];
112
+ if (this.cx !== this.cx) this.moveTo(start[0], start[1]);
113
+ else this.lineTo(start[0], start[1]);
114
+ if (r === 0) return;
115
+ let sweep = counterclockwise ? startAngle - endAngle : endAngle - startAngle;
116
+ const whole = sweep >= TAU;
117
+ sweep = whole ? TAU : (sweep % TAU + TAU) % TAU;
118
+ if (sweep === 0) return;
119
+ const flag = counterclockwise ? 0 : 1;
120
+ const end = startAngle + (counterclockwise ? -sweep : sweep);
121
+ const point = (angle) => `${num(x + r * Math.cos(angle))} ${num(y + r * Math.sin(angle))}`;
122
+ if (whole) {
123
+ const half = startAngle + (counterclockwise ? -Math.PI : Math.PI);
124
+ this.path += `A${num(r)} ${num(r)} 0 0 ${flag} ${point(half)}`;
125
+ this.path += `A${num(r)} ${num(r)} 0 0 ${flag} ${point(end)}`;
126
+ } else this.path += `A${num(r)} ${num(r)} 0 ${sweep > Math.PI ? 1 : 0} ${flag} ${point(end)}`;
127
+ this.cx = x + r * Math.cos(end);
128
+ this.cy = y + r * Math.sin(end);
129
+ }
130
+ arcTo(x1, y1, x2, y2, radius) {
131
+ if (this.cx !== this.cx) return this.moveTo(x1, y1);
132
+ const [x0, y0] = [this.cx, this.cy];
133
+ const ax = x0 - x1;
134
+ const ay = y0 - y1;
135
+ const bx = x2 - x1;
136
+ const by = y2 - y1;
137
+ const la = Math.hypot(ax, ay);
138
+ const lb = Math.hypot(bx, by);
139
+ const cross = ax * by - ay * bx;
140
+ if (radius <= 0 || la === 0 || lb === 0 || Math.abs(cross) < 1e-9 * la * lb) return this.lineTo(x1, y1);
141
+ const angle = Math.acos(Math.max(-1, Math.min(1, (ax * bx + ay * by) / (la * lb))));
142
+ const along = radius / Math.tan(angle / 2);
143
+ this.lineTo(x1 + ax / la * along, y1 + ay / la * along);
144
+ const ex = x1 + bx / lb * along;
145
+ const ey = y1 + by / lb * along;
146
+ this.path += `A${num(radius)} ${num(radius)} 0 0 ${cross < 0 ? 1 : 0} ${num(ex)} ${num(ey)}`;
147
+ this.cx = ex;
148
+ this.cy = ey;
149
+ }
150
+ setLineDash(segments) {
151
+ this.dash = [...segments];
152
+ }
153
+ fill(rule) {
154
+ if (!this.path) return;
155
+ const evenodd = rule === "evenodd" ? " fill-rule=\"evenodd\"" : "";
156
+ this.body.push(`<path d="${this.path}"${paintAttribute("fill", this.color(String(this.fillStyle)))}${evenodd}${this.common()}/>`);
157
+ }
158
+ stroke() {
159
+ if (!this.path || this.lineWidth <= 0) return;
160
+ let attributes = ` fill="none"${paintAttribute("stroke", this.color(String(this.strokeStyle)))}`;
161
+ if (this.lineWidth !== 1) attributes += ` stroke-width="${num(this.lineWidth)}"`;
162
+ if (this.lineJoin !== "miter") attributes += ` stroke-linejoin="${this.lineJoin}"`;
163
+ else attributes += " stroke-miterlimit=\"10\"";
164
+ if (this.lineCap !== "butt") attributes += ` stroke-linecap="${this.lineCap}"`;
165
+ if (this.dash.length) attributes += ` stroke-dasharray="${this.dash.map(num).join(" ")}"`;
166
+ this.body.push(`<path d="${this.path}"${attributes}${this.common()}/>`);
167
+ }
168
+ clip() {
169
+ const id = `${this.prefix}c${this.clips++}`;
170
+ const within = this.clipId ? ` clip-path="url(#${this.clipId})"` : "";
171
+ this.defs.push(`<clipPath id="${id}"${within}><path d="${this.path || "M0 0"}"/></clipPath>`);
172
+ this.clipId = id;
173
+ }
174
+ drawImage(image, ...args) {
175
+ let href = "";
176
+ if (typeof HTMLCanvasElement !== "undefined" && image instanceof HTMLCanvasElement) href = image.toDataURL("image/png");
177
+ else if (typeof HTMLImageElement !== "undefined" && image instanceof HTMLImageElement) href = image.currentSrc || image.src;
178
+ if (!href) return;
179
+ const size = image;
180
+ const [x = 0, y = 0, width = size.width, height = size.height] = args.length >= 8 ? args.slice(4) : args;
181
+ this.body.push(`<image href="${escapeXml(href)}" x="${num(x)}" y="${num(y)}" width="${num(width)}" height="${num(height)}" preserveAspectRatio="none"${this.common()}/>`);
182
+ }
183
+ /** Draws what `draw` paints as one layer, `alpha` see-through as a whole. */
184
+ layer(alpha, draw) {
185
+ this.body.push(`<g opacity="${num(alpha)}">`);
186
+ draw();
187
+ this.body.push("</g>");
188
+ }
189
+ /** Adds markup as it is, for what the painter can't draw (text). */
190
+ raw(markup) {
191
+ this.body.push(markup);
192
+ }
193
+ /** The clip and see-through every shape takes from the state. */
194
+ common() {
195
+ let attributes = "";
196
+ if (this.globalAlpha < 1) attributes += ` opacity="${num(this.globalAlpha)}"`;
197
+ if (this.clipId) attributes += ` clip-path="url(#${this.clipId})"`;
198
+ return attributes;
199
+ }
200
+ /** What was drawn, as the inside of an `<svg>`. */
201
+ markup() {
202
+ return (this.defs.length ? `<defs>${this.defs.join("")}</defs>` : "") + this.body.join("");
203
+ }
204
+ };
205
+ /** Whether a painter writes SVG: it then draws layers and text itself. */
206
+ var isSvg = (painter) => painter instanceof SvgPainter;
207
+ export { SvgPainter, escapeXml, isSvg, num, paintAttribute };
208
+
209
+ //# sourceMappingURL=svg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svg.js","names":[],"sources":["../../src/core/svg.ts"],"sourcesContent":["import type { Painter } from './painter'\n\n/*\n * A painter that writes SVG instead of pixels. The chart draws into it\n * with the same code it draws onto the canvas, so an exported or\n * server-rendered SVG looks the same as the chart in the page.\n */\n\nconst TAU = Math.PI * 2\n\n/** A number with at most two decimals, short in the markup. */\nexport const num = (value: number) => String(Math.round(value * 100) / 100)\n\nconst ESCAPES: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n}\n\n/** Text made safe to put in markup, as content or in an attribute. */\nexport const escapeXml = (text: string) => text.replace(/[&<>\"']/g, (c) => ESCAPES[c]!)\n\n/** `name=\"value\"` for a color, or a style when SVG attributes can't take it (`var(…)`, `color-mix(…)`). */\nexport function paintAttribute(name: 'fill' | 'stroke' | 'stop-color', color: string): string {\n return /^(var|color-mix)\\(/.test(color)\n ? ` style=\"${name}:${escapeXml(color)}\"`\n : ` ${name}=\"${escapeXml(color)}\"`\n}\n\ninterface State {\n fillStyle: string\n strokeStyle: string\n lineWidth: number\n lineJoin: CanvasLineJoin\n lineCap: CanvasLineCap\n globalAlpha: number\n dash: number[]\n clip: string | null\n}\n\nexport class SvgPainter implements Painter {\n fillStyle: string | CanvasGradient | CanvasPattern = '#000'\n strokeStyle: string | CanvasGradient | CanvasPattern = '#000'\n lineWidth = 1\n lineJoin: CanvasLineJoin = 'miter'\n lineCap: CanvasLineCap = 'butt'\n globalAlpha = 1\n\n private dash: number[] = []\n private clipId: string | null = null\n private states: State[] = []\n private path = ''\n /** The current point, and where the current subpath starts; `NaN` when there is none. */\n private cx = Number.NaN\n private cy = Number.NaN\n private sx = Number.NaN\n private sy = Number.NaN\n private body: string[] = []\n private defs: string[] = []\n private clips = 0\n private ids = 0\n\n /**\n * `prefix` starts every id the markup gives, so two SVGs in one page\n * don't share them; `color` turns a color into one the SVG's reader\n * understands.\n */\n constructor(\n private prefix: string,\n private color: (css: string) => string = (css) => css,\n ) {}\n\n /** An id of its own in this SVG, for something drawn as raw markup (a gradient). */\n id(name: string): string {\n return `${this.prefix}${name}${this.ids++}`\n }\n\n save(): void {\n this.states.push({\n fillStyle: String(this.fillStyle),\n strokeStyle: String(this.strokeStyle),\n lineWidth: this.lineWidth,\n lineJoin: this.lineJoin,\n lineCap: this.lineCap,\n globalAlpha: this.globalAlpha,\n dash: this.dash,\n clip: this.clipId,\n })\n }\n\n restore(): void {\n const state = this.states.pop()\n if (!state) return\n this.fillStyle = state.fillStyle\n this.strokeStyle = state.strokeStyle\n this.lineWidth = state.lineWidth\n this.lineJoin = state.lineJoin\n this.lineCap = state.lineCap\n this.globalAlpha = state.globalAlpha\n this.dash = state.dash\n this.clipId = state.clip\n }\n\n beginPath(): void {\n this.path = ''\n this.cx = this.cy = this.sx = this.sy = Number.NaN\n }\n\n closePath(): void {\n if (!this.path) return\n this.path += 'Z'\n this.cx = this.sx\n this.cy = this.sy\n }\n\n moveTo(x: number, y: number): void {\n this.path += `M${num(x)} ${num(y)}`\n this.cx = this.sx = x\n this.cy = this.sy = y\n }\n\n lineTo(x: number, y: number): void {\n // a line with no point before it starts there, as on a canvas\n if (this.cx !== this.cx) return this.moveTo(x, y)\n this.path += `L${num(x)} ${num(y)}`\n this.cx = x\n this.cy = y\n }\n\n bezierCurveTo(c1x: number, c1y: number, c2x: number, c2y: number, x: number, y: number): void {\n if (this.cx !== this.cx) this.moveTo(c1x, c1y)\n this.path += `C${num(c1x)} ${num(c1y)} ${num(c2x)} ${num(c2y)} ${num(x)} ${num(y)}`\n this.cx = x\n this.cy = y\n }\n\n rect(x: number, y: number, width: number, height: number): void {\n this.path += `M${num(x)} ${num(y)}h${num(width)}v${num(height)}h${num(-width)}Z`\n this.cx = this.sx = x\n this.cy = this.sy = y\n }\n\n arc(\n x: number,\n y: number,\n radius: number,\n startAngle: number,\n endAngle: number,\n counterclockwise = false,\n ): void {\n const r = Math.abs(radius)\n const start: [number, number] = [x + r * Math.cos(startAngle), y + r * Math.sin(startAngle)]\n // a canvas draws a line from the current point to where the arc starts\n if (this.cx !== this.cx) this.moveTo(start[0], start[1])\n else this.lineTo(start[0], start[1])\n if (r === 0) return\n let sweep = counterclockwise ? startAngle - endAngle : endAngle - startAngle\n const whole = sweep >= TAU\n sweep = whole ? TAU : ((sweep % TAU) + TAU) % TAU\n if (sweep === 0) return\n const flag = counterclockwise ? 0 : 1\n const end = startAngle + (counterclockwise ? -sweep : sweep)\n const point = (angle: number) =>\n `${num(x + r * Math.cos(angle))} ${num(y + r * Math.sin(angle))}`\n if (whole) {\n // a whole circle is two halves: one arc can't end where it starts\n const half = startAngle + (counterclockwise ? -Math.PI : Math.PI)\n this.path += `A${num(r)} ${num(r)} 0 0 ${flag} ${point(half)}`\n this.path += `A${num(r)} ${num(r)} 0 0 ${flag} ${point(end)}`\n } else {\n this.path += `A${num(r)} ${num(r)} 0 ${sweep > Math.PI ? 1 : 0} ${flag} ${point(end)}`\n }\n this.cx = x + r * Math.cos(end)\n this.cy = y + r * Math.sin(end)\n }\n\n arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void {\n if (this.cx !== this.cx) return this.moveTo(x1, y1)\n const [x0, y0] = [this.cx, this.cy]\n const ax = x0 - x1\n const ay = y0 - y1\n const bx = x2 - x1\n const by = y2 - y1\n const la = Math.hypot(ax, ay)\n const lb = Math.hypot(bx, by)\n const cross = ax * by - ay * bx\n // no corner to round: a straight line to the corner point\n if (radius <= 0 || la === 0 || lb === 0 || Math.abs(cross) < 1e-9 * la * lb)\n return this.lineTo(x1, y1)\n const angle = Math.acos(Math.max(-1, Math.min(1, (ax * bx + ay * by) / (la * lb))))\n const along = radius / Math.tan(angle / 2)\n this.lineTo(x1 + (ax / la) * along, y1 + (ay / la) * along)\n const ex = x1 + (bx / lb) * along\n const ey = y1 + (by / lb) * along\n this.path += `A${num(radius)} ${num(radius)} 0 0 ${cross < 0 ? 1 : 0} ${num(ex)} ${num(ey)}`\n this.cx = ex\n this.cy = ey\n }\n\n setLineDash(segments: Iterable<number>): void {\n this.dash = [...segments]\n }\n\n fill(rule?: CanvasFillRule | Path2D): void {\n if (!this.path) return\n const evenodd = rule === 'evenodd' ? ' fill-rule=\"evenodd\"' : ''\n this.body.push(\n `<path d=\"${this.path}\"${paintAttribute('fill', this.color(String(this.fillStyle)))}${evenodd}${this.common()}/>`,\n )\n }\n\n stroke(): void {\n if (!this.path || this.lineWidth <= 0) return\n let attributes = ` fill=\"none\"${paintAttribute('stroke', this.color(String(this.strokeStyle)))}`\n if (this.lineWidth !== 1) attributes += ` stroke-width=\"${num(this.lineWidth)}\"`\n if (this.lineJoin !== 'miter') attributes += ` stroke-linejoin=\"${this.lineJoin}\"`\n // a canvas cuts miters off later than SVG does by default\n else attributes += ' stroke-miterlimit=\"10\"'\n if (this.lineCap !== 'butt') attributes += ` stroke-linecap=\"${this.lineCap}\"`\n if (this.dash.length) attributes += ` stroke-dasharray=\"${this.dash.map(num).join(' ')}\"`\n this.body.push(`<path d=\"${this.path}\"${attributes}${this.common()}/>`)\n }\n\n clip(): void {\n const id = `${this.prefix}c${this.clips++}`\n const within = this.clipId ? ` clip-path=\"url(#${this.clipId})\"` : ''\n this.defs.push(`<clipPath id=\"${id}\"${within}><path d=\"${this.path || 'M0 0'}\"/></clipPath>`)\n this.clipId = id\n }\n\n drawImage(image: CanvasImageSource, ...args: number[]): void {\n let href = ''\n if (typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement)\n href = image.toDataURL('image/png')\n else if (typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement)\n href = image.currentSrc || image.src\n if (!href) return\n const size = image as { width: number; height: number }\n // only where it goes: a part of the image (nine numbers) is drawn whole\n const [x = 0, y = 0, width = size.width, height = size.height] =\n args.length >= 8 ? args.slice(4) : args\n this.body.push(\n `<image href=\"${escapeXml(href)}\" x=\"${num(x)}\" y=\"${num(y)}\" width=\"${num(width)}\" height=\"${num(height)}\" preserveAspectRatio=\"none\"${this.common()}/>`,\n )\n }\n\n /** Draws what `draw` paints as one layer, `alpha` see-through as a whole. */\n layer(alpha: number, draw: () => void): void {\n this.body.push(`<g opacity=\"${num(alpha)}\">`)\n draw()\n this.body.push('</g>')\n }\n\n /** Adds markup as it is, for what the painter can't draw (text). */\n raw(markup: string): void {\n this.body.push(markup)\n }\n\n /** The clip and see-through every shape takes from the state. */\n private common(): string {\n let attributes = ''\n if (this.globalAlpha < 1) attributes += ` opacity=\"${num(this.globalAlpha)}\"`\n if (this.clipId) attributes += ` clip-path=\"url(#${this.clipId})\"`\n return attributes\n }\n\n /** What was drawn, as the inside of an `<svg>`. */\n markup(): string {\n const defs = this.defs.length ? `<defs>${this.defs.join('')}</defs>` : ''\n return defs + this.body.join('')\n }\n}\n\n/** Whether a painter writes SVG: it then draws layers and text itself. */\nexport const isSvg = (painter: unknown): painter is SvgPainter => painter instanceof SvgPainter\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ // src/core/sync.ts
2
+ var groups = /* @__PURE__ */ new Map();
3
+ /** Listens to the x shown in `group`; gives back how to stop. */
4
+ function joinGroup(group, listener) {
5
+ let members = groups.get(group);
6
+ if (!members) groups.set(group, members = /* @__PURE__ */ new Set());
7
+ members.add(listener);
8
+ return () => {
9
+ members.delete(listener);
10
+ if (!members.size) groups.delete(group);
11
+ };
12
+ }
13
+ /** Shows `x` in every chart of `group` but the one telling (`from`). */
14
+ function tellGroup(group, x, from) {
15
+ const members = groups.get(group);
16
+ if (!members) return;
17
+ for (const listener of members) if (listener !== from) listener(x);
18
+ }
19
+ export { joinGroup, tellGroup };
20
+
21
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","names":[],"sources":["../../src/core/sync.ts"],"sourcesContent":["/*\n * Charts whose tooltips share a `group`: the x under the pointer in one is\n * shown in the others. An x travels as the data has it, so charts with x\n * read differently still meet: a category's name, a time in ms, a number.\n */\n\n/** An x as charts of a group share it; `null` when the pointer left. */\nexport type SharedX = string | number | null\n\ntype Listener = (x: SharedX) => void\n\nconst groups = new Map<string, Set<Listener>>()\n\n/** Listens to the x shown in `group`; gives back how to stop. */\nexport function joinGroup(group: string, listener: Listener): () => void {\n let members = groups.get(group)\n if (!members) groups.set(group, (members = new Set()))\n members.add(listener)\n return () => {\n members.delete(listener)\n if (!members.size) groups.delete(group)\n }\n}\n\n/** Shows `x` in every chart of `group` but the one telling (`from`). */\nexport function tellGroup(group: string, x: SharedX, from: Listener): void {\n const members = groups.get(group)\n if (!members) return\n for (const listener of members) if (listener !== from) listener(x)\n}\n"],"mappings":";AAWA,IAAM,yBAAS,IAAI,IAA2B;;AAG9C,SAAgB,UAAU,OAAe,UAAgC;CACvE,IAAI,UAAU,OAAO,IAAI,KAAK;CAC9B,IAAI,CAAC,SAAS,OAAO,IAAI,OAAQ,0BAAU,IAAI,IAAI,CAAE;CACrD,QAAQ,IAAI,QAAQ;CACpB,aAAa;EACX,QAAQ,OAAO,QAAQ;EACvB,IAAI,CAAC,QAAQ,MAAM,OAAO,OAAO,KAAK;CACxC;AACF;;AAGA,SAAgB,UAAU,OAAe,GAAY,MAAsB;CACzE,MAAM,UAAU,OAAO,IAAI,KAAK;CAChC,IAAI,CAAC,SAAS;CACd,KAAK,MAAM,YAAY,SAAS,IAAI,aAAa,MAAM,SAAS,CAAC;AACnE"}