@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,151 @@
1
+ import { stepDecimals } from "./scale.js";
2
+ import { floorTime, parentUnit, startsUnit } from "./time.js";
3
+ // src/core/format.ts
4
+ var formatters = /* @__PURE__ */ new Map();
5
+ function numberFormat(locale, options) {
6
+ const key = `n:${locale}:${JSON.stringify(options)}`;
7
+ let format = formatters.get(key);
8
+ if (!format) formatters.set(key, format = new Intl.NumberFormat(locale, options));
9
+ return format;
10
+ }
11
+ function dateFormat(locale, options) {
12
+ const key = `d:${locale}:${JSON.stringify(options)}`;
13
+ let format = formatters.get(key);
14
+ if (!format) formatters.set(key, format = new Intl.DateTimeFormat(locale, options));
15
+ return format;
16
+ }
17
+ /**
18
+ * Formats the ticks of a number axis: as many decimals as the step needs,
19
+ * and in short form ("12万", "1.2M") once the numbers get long, with as
20
+ * many digits as it takes to tell the ticks apart. With `years`, whole
21
+ * numbers under 10000 go without separators, so 2019 doesn't read "2,019".
22
+ */
23
+ function numberTickFormat(locale, ticks, { years = false } = {}) {
24
+ const step = ticks.length > 1 ? Math.abs(ticks[1] - ticks[0]) : 0;
25
+ if (Math.max(0, ...ticks.map(Math.abs)) >= 1e5 && step >= 1e3) for (let digits = 2;; digits++) {
26
+ const compact = numberFormat(locale, {
27
+ notation: "compact",
28
+ maximumFractionDigits: digits
29
+ });
30
+ const texts = ticks.map((tick) => compact.format(tick));
31
+ if (new Set(texts).size === texts.length || digits >= 8) return (value) => compact.format(value);
32
+ }
33
+ let decimals = step > 0 ? stepDecimals(step) : 2;
34
+ for (const tick of ticks) {
35
+ const text = String(Number(tick.toPrecision(12)));
36
+ const dot = text.indexOf(".");
37
+ if (dot >= 0 && !text.includes("e")) decimals = Math.max(decimals, text.length - dot - 1);
38
+ }
39
+ const grouping = !(years && ticks.every((tick) => Number.isInteger(tick) && Math.abs(tick) < 1e4));
40
+ const plain = numberFormat(locale, {
41
+ maximumFractionDigits: decimals,
42
+ useGrouping: grouping
43
+ });
44
+ return (value) => plain.format(value);
45
+ }
46
+ /** Formats a value for reading in full (tooltips, the accessible summary). */
47
+ function numberFormatFull(locale) {
48
+ const plain = numberFormat(locale, { maximumFractionDigits: 4 });
49
+ return (value) => plain.format(value);
50
+ }
51
+ var UNIT_FORMATS = {
52
+ second: {
53
+ hour: "2-digit",
54
+ minute: "2-digit",
55
+ second: "2-digit",
56
+ hourCycle: "h23"
57
+ },
58
+ minute: {
59
+ hour: "2-digit",
60
+ minute: "2-digit",
61
+ hourCycle: "h23"
62
+ },
63
+ hour: {
64
+ hour: "2-digit",
65
+ minute: "2-digit",
66
+ hourCycle: "h23"
67
+ },
68
+ day: { day: "numeric" },
69
+ week: { day: "numeric" },
70
+ month: { month: "short" },
71
+ year: { year: "numeric" }
72
+ };
73
+ var CONTEXT_FORMATS = {
74
+ minute: {
75
+ month: "short",
76
+ day: "numeric"
77
+ },
78
+ day: {
79
+ month: "short",
80
+ day: "numeric"
81
+ },
82
+ month: {
83
+ year: "numeric",
84
+ month: "short"
85
+ }
86
+ };
87
+ /**
88
+ * Formats the ticks of a time axis. Most ticks show only the unit they
89
+ * step by ("15日", "09:00"); the first one and the first in each bigger
90
+ * unit say more ("11月2日", "2026年1月"), so the axis reads without a legend.
91
+ * `previous` is the tick before, if any.
92
+ */
93
+ function timeTickFormat(locale, interval) {
94
+ const unit = interval.unit;
95
+ const plain = dateFormat(locale, UNIT_FORMATS[unit]);
96
+ const parent = parentUnit(unit);
97
+ const context = CONTEXT_FORMATS[unit === "hour" || unit === "second" ? "minute" : unit === "week" ? "day" : unit];
98
+ const contextFormat = context ? dateFormat(locale, context) : null;
99
+ const dayOnly = unit === "hour" || unit === "minute" || unit === "second";
100
+ return (value, previous) => {
101
+ const startsParent = parent !== null && previous !== void 0 && floorTime(previous, parent).getTime() !== floorTime(value, parent).getTime();
102
+ if (contextFormat && (previous === void 0 || startsParent)) {
103
+ if (dayOnly && !startsUnit(value, "day")) return plain.format(value);
104
+ if (unit === "month" && startsParent) return dateFormat(locale, { year: "numeric" }).format(value);
105
+ return contextFormat.format(value);
106
+ }
107
+ return plain.format(value);
108
+ };
109
+ }
110
+ /** How fine a time must be written for points `step` ms apart: 0 days, 1 minutes, 2 seconds, 4 to 6 parts of a second. */
111
+ var rank = (step) => step < 1e3 ? 3 + (step >= 100 ? 1 : step >= 10 ? 2 : 3) : step < 6e4 ? 2 : step < 864e5 ? 1 : 0;
112
+ /**
113
+ * How precisely a point in time needs writing: as precise as the time
114
+ * itself (09:30 needs minutes), and as its neighbor is near (points 100ms
115
+ * apart need tenths of a second, even on a whole second). `digits` are
116
+ * the digits of a second, from 1 to 3.
117
+ */
118
+ function timePrecision(value, gap = Number.POSITIVE_INFINITY) {
119
+ const date = new Date(value);
120
+ const ms = date.getMilliseconds();
121
+ const own = ms !== 0 ? rank(ms % 100 === 0 ? 100 : ms % 10 === 0 ? 10 : 1) : date.getSeconds() !== 0 ? 2 : date.getMinutes() !== 0 || date.getHours() !== 0 ? 1 : 0;
122
+ const r = Math.max(own, gap > 0 ? rank(gap) : 0);
123
+ return {
124
+ unit: r === 0 ? "day" : r === 1 ? "minute" : "second",
125
+ digits: r > 3 ? r - 3 : 0
126
+ };
127
+ }
128
+ /** Formats a point in time in full, as precise as `unit` (tooltips, the accessible summary). */
129
+ function timeFormatFull(locale, unit = "day", digits = 0) {
130
+ const format = dateFormat(locale, unit === "year" ? { year: "numeric" } : unit === "month" ? {
131
+ year: "numeric",
132
+ month: "short"
133
+ } : unit === "day" || unit === "week" ? {
134
+ year: "numeric",
135
+ month: "short",
136
+ day: "numeric"
137
+ } : {
138
+ year: "numeric",
139
+ month: "short",
140
+ day: "numeric",
141
+ hour: "2-digit",
142
+ minute: "2-digit",
143
+ second: unit === "second" ? "2-digit" : void 0,
144
+ fractionalSecondDigits: unit === "second" && digits ? digits : void 0,
145
+ hourCycle: "h23"
146
+ });
147
+ return (value) => format.format(value);
148
+ }
149
+ export { numberFormatFull, numberTickFormat, timeFormatFull, timePrecision, timeTickFormat };
150
+
151
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","names":[],"sources":["../../src/core/format.ts"],"sourcesContent":["import { stepDecimals } from './scale'\nimport { floorTime, parentUnit, startsUnit, type TimeInterval, type TimeUnit } from './time'\n\nconst formatters = new Map<string, Intl.NumberFormat | Intl.DateTimeFormat>()\n\nfunction numberFormat(locale: string, options: Intl.NumberFormatOptions): Intl.NumberFormat {\n const key = `n:${locale}:${JSON.stringify(options)}`\n let format = formatters.get(key) as Intl.NumberFormat | undefined\n if (!format) formatters.set(key, (format = new Intl.NumberFormat(locale, options)))\n return format\n}\n\nfunction dateFormat(locale: string, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {\n const key = `d:${locale}:${JSON.stringify(options)}`\n let format = formatters.get(key) as Intl.DateTimeFormat | undefined\n if (!format) formatters.set(key, (format = new Intl.DateTimeFormat(locale, options)))\n return format\n}\n\n/**\n * Formats the ticks of a number axis: as many decimals as the step needs,\n * and in short form (\"12万\", \"1.2M\") once the numbers get long, with as\n * many digits as it takes to tell the ticks apart. With `years`, whole\n * numbers under 10000 go without separators, so 2019 doesn't read \"2,019\".\n */\nexport function numberTickFormat(\n locale: string,\n ticks: number[],\n { years = false }: { years?: boolean } = {},\n): (value: number) => string {\n const step = ticks.length > 1 ? Math.abs(ticks[1]! - ticks[0]!) : 0\n const largest = Math.max(0, ...ticks.map(Math.abs))\n if (largest >= 1e5 && step >= 1e3) {\n for (let digits = 2; ; digits++) {\n const compact = numberFormat(locale, { notation: 'compact', maximumFractionDigits: digits })\n const texts = ticks.map((tick) => compact.format(tick))\n if (new Set(texts).size === texts.length || digits >= 8)\n return (value) => compact.format(value)\n }\n }\n // enough decimals for every tick (explicit ticks need not be multiples of a power of ten)\n let decimals = step > 0 ? stepDecimals(step) : 2\n for (const tick of ticks) {\n const text = String(Number(tick.toPrecision(12)))\n const dot = text.indexOf('.')\n if (dot >= 0 && !text.includes('e')) decimals = Math.max(decimals, text.length - dot - 1)\n }\n const grouping = !(years && ticks.every((tick) => Number.isInteger(tick) && Math.abs(tick) < 1e4))\n const plain = numberFormat(locale, { maximumFractionDigits: decimals, useGrouping: grouping })\n return (value) => plain.format(value)\n}\n\n/** Formats a value for reading in full (tooltips, the accessible summary). */\nexport function numberFormatFull(locale: string): (value: number) => string {\n const plain = numberFormat(locale, { maximumFractionDigits: 4 })\n return (value) => plain.format(value)\n}\n\nconst UNIT_FORMATS: Record<TimeUnit, Intl.DateTimeFormatOptions> = {\n second: { hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23' },\n minute: { hour: '2-digit', minute: '2-digit', hourCycle: 'h23' },\n hour: { hour: '2-digit', minute: '2-digit', hourCycle: 'h23' },\n day: { day: 'numeric' },\n week: { day: 'numeric' },\n month: { month: 'short' },\n year: { year: 'numeric' },\n}\n\nconst CONTEXT_FORMATS: Partial<Record<TimeUnit, Intl.DateTimeFormatOptions>> = {\n // the first tick, and each new day / month / year, says which one it is\n minute: { month: 'short', day: 'numeric' },\n day: { month: 'short', day: 'numeric' },\n month: { year: 'numeric', month: 'short' },\n}\n\n/**\n * Formats the ticks of a time axis. Most ticks show only the unit they\n * step by (\"15日\", \"09:00\"); the first one and the first in each bigger\n * unit say more (\"11月2日\", \"2026年1月\"), so the axis reads without a legend.\n * `previous` is the tick before, if any.\n */\nexport function timeTickFormat(\n locale: string,\n interval: TimeInterval,\n): (value: number, previous?: number) => string {\n const unit = interval.unit\n const plain = dateFormat(locale, UNIT_FORMATS[unit])\n const parent = parentUnit(unit)\n const contextUnit =\n unit === 'hour' || unit === 'second' ? 'minute' : unit === 'week' ? 'day' : unit\n const context = CONTEXT_FORMATS[contextUnit]\n const contextFormat = context ? dateFormat(locale, context) : null\n // hours and minutes: a new day is marked by its date alone\n const dayOnly = unit === 'hour' || unit === 'minute' || unit === 'second'\n return (value, previous) => {\n const startsParent =\n parent !== null &&\n previous !== undefined &&\n floorTime(previous, parent).getTime() !== floorTime(value, parent).getTime()\n if (contextFormat && (previous === undefined || startsParent)) {\n if (dayOnly && !startsUnit(value, 'day')) {\n // the first tick of a time-of-day axis that doesn't start at midnight\n return plain.format(value)\n }\n if (unit === 'month' && startsParent)\n return dateFormat(locale, { year: 'numeric' }).format(value)\n return contextFormat.format(value)\n }\n return plain.format(value)\n }\n}\n\n/** How fine a time must be written for points `step` ms apart: 0 days, 1 minutes, 2 seconds, 4 to 6 parts of a second. */\nconst rank = (step: number) =>\n step < 1000\n ? 3 + (step >= 100 ? 1 : step >= 10 ? 2 : 3)\n : step < 60e3\n ? 2\n : step < 86400e3\n ? 1\n : 0\n\n/**\n * How precisely a point in time needs writing: as precise as the time\n * itself (09:30 needs minutes), and as its neighbor is near (points 100ms\n * apart need tenths of a second, even on a whole second). `digits` are\n * the digits of a second, from 1 to 3.\n */\nexport function timePrecision(value: number, gap = Number.POSITIVE_INFINITY) {\n const date = new Date(value)\n const ms = date.getMilliseconds()\n const own =\n ms !== 0\n ? rank(ms % 100 === 0 ? 100 : ms % 10 === 0 ? 10 : 1)\n : date.getSeconds() !== 0\n ? 2\n : date.getMinutes() !== 0 || date.getHours() !== 0\n ? 1\n : 0\n const r = Math.max(own, gap > 0 ? rank(gap) : 0)\n const unit: TimeUnit = r === 0 ? 'day' : r === 1 ? 'minute' : 'second'\n return { unit, digits: (r > 3 ? r - 3 : 0) as 0 | 1 | 2 | 3 }\n}\n\n/** Formats a point in time in full, as precise as `unit` (tooltips, the accessible summary). */\nexport function timeFormatFull(\n locale: string,\n unit: TimeUnit = 'day',\n /** Digits of a second, for times less than a second apart. */\n digits: 0 | 1 | 2 | 3 = 0,\n): (value: number) => string {\n const options: Intl.DateTimeFormatOptions =\n unit === 'year'\n ? { year: 'numeric' }\n : unit === 'month'\n ? { year: 'numeric', month: 'short' }\n : unit === 'day' || unit === 'week'\n ? { year: 'numeric', month: 'short', day: 'numeric' }\n : {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n second: unit === 'second' ? '2-digit' : undefined,\n fractionalSecondDigits: unit === 'second' && digits ? digits : undefined,\n hourCycle: 'h23',\n }\n const format = dateFormat(locale, options)\n return (value) => format.format(value)\n}\n"],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ // src/core/funnel.ts
2
+ /** How much of a stage's row its bar takes; the rest is the band to the next. */
3
+ var FUNNEL_BAR = .7;
4
+ /** A stage's bar: its left, top, width and height, in px; `null` without a value. */
5
+ function stageBar(series, view, i) {
6
+ const x = series.x.values[i];
7
+ const v = series.top[i];
8
+ if (x !== x || v !== v) return null;
9
+ const [right, middle] = view.point(x, Math.max(0, v));
10
+ const cx = view.plot.x + view.plot.width / 2;
11
+ const half = Math.max(right - cx, .5);
12
+ return {
13
+ x: cx - half,
14
+ y: middle - view.band / 2,
15
+ width: half * 2,
16
+ height: view.band
17
+ };
18
+ }
19
+ /** The rows of a funnel in stage order, with a value. */
20
+ function stages(series) {
21
+ const order = [];
22
+ for (let i = 0; i < series.top.length; i++) if (series.x.values[i] === series.x.values[i] && series.top[i] === series.top[i]) order.push(i);
23
+ order.sort((a, b) => series.x.values[a] - series.x.values[b]);
24
+ return order;
25
+ }
26
+ var funnelKind = {
27
+ type: "funnel",
28
+ funnel: true,
29
+ zero: true,
30
+ draw(painter, series, view) {
31
+ const order = stages(series);
32
+ const bars = order.map((i) => stageBar(series, view, i));
33
+ painter.save();
34
+ painter.fillStyle = series.color;
35
+ painter.globalAlpha *= .14;
36
+ painter.beginPath();
37
+ for (let k = 0; k + 1 < bars.length; k++) {
38
+ const a = bars[k];
39
+ const b = bars[k + 1];
40
+ if (series.x.values[order[k + 1]] - series.x.values[order[k]] !== 1) continue;
41
+ painter.moveTo(a.x, a.y + a.height);
42
+ painter.lineTo(a.x + a.width, a.y + a.height);
43
+ painter.lineTo(b.x + b.width, b.y);
44
+ painter.lineTo(b.x, b.y);
45
+ painter.closePath();
46
+ }
47
+ painter.fill();
48
+ painter.restore();
49
+ const radius = Math.max(0, series.props.radius ?? 3);
50
+ painter.save();
51
+ painter.fillStyle = series.color;
52
+ painter.beginPath();
53
+ for (const bar of bars) {
54
+ const r = Math.min(radius, bar.width / 2, bar.height / 2);
55
+ painter.moveTo(bar.x + r, bar.y);
56
+ painter.arcTo(bar.x + bar.width, bar.y, bar.x + bar.width, bar.y + bar.height, r);
57
+ painter.arcTo(bar.x + bar.width, bar.y + bar.height, bar.x, bar.y + bar.height, r);
58
+ painter.arcTo(bar.x, bar.y + bar.height, bar.x, bar.y, r);
59
+ painter.arcTo(bar.x, bar.y, bar.x + bar.width, bar.y, r);
60
+ painter.closePath();
61
+ }
62
+ painter.fill();
63
+ painter.restore();
64
+ }
65
+ };
66
+ export { FUNNEL_BAR, funnelKind, stageBar };
67
+
68
+ //# sourceMappingURL=funnel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"funnel.js","names":[],"sources":["../../src/core/funnel.ts"],"sourcesContent":["import type { Painter } from './painter'\nimport type { ChartSeries, ChartSeriesKind, ChartView } from './series'\n\n/*\n * A funnel: a bar for each stage, centered and one under another in the\n * order given, as wide as its value against the largest; between two\n * stages, a lighter band from one's width to the next shows what was lost.\n */\n\nexport interface FunnelStyle {\n /** Corner radius of the bars, in px. */\n radius?: number\n}\n\n/** How much of a stage's row its bar takes; the rest is the band to the next. */\nexport const FUNNEL_BAR = 0.7\n\n/** A stage's bar: its left, top, width and height, in px; `null` without a value. */\nexport function stageBar(series: ChartSeries<FunnelStyle>, view: ChartView, i: number) {\n const x = series.x.values[i]!\n const v = series.top[i]!\n if (x !== x || v !== v) return null\n const [right, middle] = view.point(x, Math.max(0, v))\n const cx = view.plot.x + view.plot.width / 2\n const half = Math.max(right - cx, 0.5)\n return { x: cx - half, y: middle - view.band / 2, width: half * 2, height: view.band }\n}\n\n/** The rows of a funnel in stage order, with a value. */\nfunction stages(series: ChartSeries<FunnelStyle>): number[] {\n const order: number[] = []\n for (let i = 0; i < series.top.length; i++)\n if (series.x.values[i] === series.x.values[i] && series.top[i] === series.top[i]) order.push(i)\n order.sort((a, b) => series.x.values[a]! - series.x.values[b]!)\n return order\n}\n\nexport const funnelKind: ChartSeriesKind<FunnelStyle> = {\n type: 'funnel',\n funnel: true,\n zero: true,\n draw(painter: Painter, series, view) {\n const order = stages(series)\n const bars = order.map((i) => stageBar(series, view, i)!)\n // the bands between stages, under the bars\n painter.save()\n painter.fillStyle = series.color\n painter.globalAlpha *= 0.14\n painter.beginPath()\n for (let k = 0; k + 1 < bars.length; k++) {\n const a = bars[k]!\n const b = bars[k + 1]!\n // only between stages next to each other\n if (series.x.values[order[k + 1]!]! - series.x.values[order[k]!]! !== 1) continue\n painter.moveTo(a.x, a.y + a.height)\n painter.lineTo(a.x + a.width, a.y + a.height)\n painter.lineTo(b.x + b.width, b.y)\n painter.lineTo(b.x, b.y)\n painter.closePath()\n }\n painter.fill()\n painter.restore()\n const radius = Math.max(0, series.props.radius ?? 3)\n painter.save()\n painter.fillStyle = series.color\n painter.beginPath()\n for (const bar of bars) {\n const r = Math.min(radius, bar.width / 2, bar.height / 2)\n painter.moveTo(bar.x + r, bar.y)\n painter.arcTo(bar.x + bar.width, bar.y, bar.x + bar.width, bar.y + bar.height, r)\n painter.arcTo(bar.x + bar.width, bar.y + bar.height, bar.x, bar.y + bar.height, r)\n painter.arcTo(bar.x, bar.y + bar.height, bar.x, bar.y, r)\n painter.arcTo(bar.x, bar.y, bar.x + bar.width, bar.y, r)\n painter.closePath()\n }\n painter.fill()\n painter.restore()\n },\n}\n"],"mappings":";;AAeA,IAAa,aAAa;;AAG1B,SAAgB,SAAS,QAAkC,MAAiB,GAAW;CACrF,MAAM,IAAI,OAAO,EAAE,OAAO;CAC1B,MAAM,IAAI,OAAO,IAAI;CACrB,IAAI,MAAM,KAAK,MAAM,GAAG,OAAO;CAC/B,MAAM,CAAC,OAAO,UAAU,KAAK,MAAM,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC;CACpD,MAAM,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,QAAQ;CAC3C,MAAM,OAAO,KAAK,IAAI,QAAQ,IAAI,EAAG;CACrC,OAAO;EAAE,GAAG,KAAK;EAAM,GAAG,SAAS,KAAK,OAAO;EAAG,OAAO,OAAO;EAAG,QAAQ,KAAK;CAAK;AACvF;;AAGA,SAAS,OAAO,QAA4C;CAC1D,MAAM,QAAkB,CAAC;CACzB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,IAAI,QAAQ,KACrC,IAAI,OAAO,EAAE,OAAO,OAAO,OAAO,EAAE,OAAO,MAAM,OAAO,IAAI,OAAO,OAAO,IAAI,IAAI,MAAM,KAAK,CAAC;CAChG,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,OAAO,KAAM,OAAO,EAAE,OAAO,EAAG;CAC9D,OAAO;AACT;AAEA,IAAa,aAA2C;CACtD,MAAM;CACN,QAAQ;CACR,MAAM;CACN,KAAK,SAAkB,QAAQ,MAAM;EACnC,MAAM,QAAQ,OAAO,MAAM;EAC3B,MAAM,OAAO,MAAM,KAAK,MAAM,SAAS,QAAQ,MAAM,CAAC,CAAE;EAExD,QAAQ,KAAK;EACb,QAAQ,YAAY,OAAO;EAC3B,QAAQ,eAAe;EACvB,QAAQ,UAAU;EAClB,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK,QAAQ,KAAK;GACxC,MAAM,IAAI,KAAK;GACf,MAAM,IAAI,KAAK,IAAI;GAEnB,IAAI,OAAO,EAAE,OAAO,MAAM,IAAI,MAAQ,OAAO,EAAE,OAAO,MAAM,QAAU,GAAG;GACzE,QAAQ,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM;GAClC,QAAQ,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM;GAC5C,QAAQ,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;GACjC,QAAQ,OAAO,EAAE,GAAG,EAAE,CAAC;GACvB,QAAQ,UAAU;EACpB;EACA,QAAQ,KAAK;EACb,QAAQ,QAAQ;EAChB,MAAM,SAAS,KAAK,IAAI,GAAG,OAAO,MAAM,UAAU,CAAC;EACnD,QAAQ,KAAK;EACb,QAAQ,YAAY,OAAO;EAC3B,QAAQ,UAAU;EAClB,KAAK,MAAM,OAAO,MAAM;GACtB,MAAM,IAAI,KAAK,IAAI,QAAQ,IAAI,QAAQ,GAAG,IAAI,SAAS,CAAC;GACxD,QAAQ,OAAO,IAAI,IAAI,GAAG,IAAI,CAAC;GAC/B,QAAQ,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,GAAG,IAAI,IAAI,IAAI,OAAO,IAAI,IAAI,IAAI,QAAQ,CAAC;GAChF,QAAQ,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,IAAI,IAAI,QAAQ,IAAI,GAAG,IAAI,IAAI,IAAI,QAAQ,CAAC;GACjF,QAAQ,MAAM,IAAI,GAAG,IAAI,IAAI,IAAI,QAAQ,IAAI,GAAG,IAAI,GAAG,CAAC;GACxD,QAAQ,MAAM,IAAI,GAAG,IAAI,GAAG,IAAI,IAAI,IAAI,OAAO,IAAI,GAAG,CAAC;GACvD,QAAQ,UAAU;EACpB;EACA,QAAQ,KAAK;EACb,QAAQ,QAAQ;CAClB;AACF"}
@@ -0,0 +1,87 @@
1
+ // src/core/grid.ts
2
+ /** About this many points to a cell. */
3
+ var PER_CELL = 4;
4
+ /** At most this many cells along each side. */
5
+ var MOST = 1024;
6
+ var grids = /* @__PURE__ */ new WeakMap();
7
+ /** The grid of the points `(xs[i], ys[i])`; points with a `NaN` are left out. Cached per data. */
8
+ function pointGrid(xs, ys) {
9
+ const known = grids.get(ys);
10
+ if (known && known.xs === xs) return known.grid;
11
+ const n = Math.min(xs.length, ys.length);
12
+ let x0 = Infinity;
13
+ let x1 = -Infinity;
14
+ let y0 = Infinity;
15
+ let y1 = -Infinity;
16
+ let count = 0;
17
+ for (let i = 0; i < n; i++) {
18
+ const x = xs[i];
19
+ const y = ys[i];
20
+ if (x !== x || y !== y) continue;
21
+ count++;
22
+ if (x < x0) x0 = x;
23
+ if (x > x1) x1 = x;
24
+ if (y < y0) y0 = y;
25
+ if (y > y1) y1 = y;
26
+ }
27
+ const side = Math.min(MOST, Math.max(1, Math.ceil(Math.sqrt(count / PER_CELL))));
28
+ const columns = x1 > x0 ? side : 1;
29
+ const rows = y1 > y0 ? side : 1;
30
+ const width = (x1 - x0) / columns || 1;
31
+ const height = (y1 - y0) / rows || 1;
32
+ const cells = new Int32Array(n);
33
+ const starts = new Int32Array(columns * rows + 1);
34
+ for (let i = 0; i < n; i++) {
35
+ const x = xs[i];
36
+ const y = ys[i];
37
+ if (x !== x || y !== y) {
38
+ cells[i] = -1;
39
+ continue;
40
+ }
41
+ const c = Math.min(columns - 1, Math.floor((x - x0) / width));
42
+ const at = Math.min(rows - 1, Math.floor((y - y0) / height)) * columns + c;
43
+ cells[i] = at;
44
+ starts[at + 1]++;
45
+ }
46
+ for (let k = 1; k < starts.length; k++) starts[k] += starts[k - 1];
47
+ const items = new Int32Array(count);
48
+ const fill = starts.slice(0, -1);
49
+ for (let i = 0; i < n; i++) {
50
+ const at = cells[i];
51
+ if (at >= 0) items[fill[at]++] = i;
52
+ }
53
+ const grid = {
54
+ x0,
55
+ y0,
56
+ columns,
57
+ rows,
58
+ width,
59
+ height,
60
+ starts,
61
+ items
62
+ };
63
+ grids.set(ys, {
64
+ xs,
65
+ grid
66
+ });
67
+ return grid;
68
+ }
69
+ /** The cell along one side that `value` is in, kept on the grid. */
70
+ var cell = (value, origin, size, most) => Math.min(most - 1, Math.max(0, Math.floor((value - origin) / size)));
71
+ /** Calls `visit` with each point in the cells that touch the data box from `(xa, ya)` to `(xb, yb)`. */
72
+ function eachNear(grid, xa, xb, ya, yb, visit) {
73
+ const { x0, y0, columns, rows, width, height, starts, items } = grid;
74
+ const c0 = cell(Math.min(xa, xb), x0, width, columns);
75
+ const c1 = cell(Math.max(xa, xb), x0, width, columns);
76
+ const r0 = cell(Math.min(ya, yb), y0, height, rows);
77
+ const r1 = cell(Math.max(ya, yb), y0, height, rows);
78
+ if (Math.max(xa, xb) < x0 || Math.min(xa, xb) > x0 + width * columns) return;
79
+ if (Math.max(ya, yb) < y0 || Math.min(ya, yb) > y0 + height * rows) return;
80
+ for (let r = r0; r <= r1; r++) for (let c = c0; c <= c1; c++) {
81
+ const at = r * columns + c;
82
+ for (let k = starts[at], end = starts[at + 1]; k < end; k++) visit(items[k]);
83
+ }
84
+ }
85
+ export { eachNear, pointGrid };
86
+
87
+ //# sourceMappingURL=grid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grid.js","names":[],"sources":["../../src/core/grid.ts"],"sourcesContent":["/*\n * A grid over scattered points, for finding the ones near the pointer\n * without going through all of them: each cell lists the points in it.\n * Made once per data, in the data's own numbers, so it holds while zooming.\n */\n\nexport interface PointGrid {\n /** The data's x and y stretch the cells cover. */\n x0: number\n y0: number\n /** Cells across and down, and the size of one in data numbers. */\n columns: number\n rows: number\n width: number\n height: number\n /** Where each cell's points start in `items`; the cell after ends them. */\n starts: Int32Array\n /** The points, cell by cell. */\n items: Int32Array\n}\n\n/** About this many points to a cell. */\nconst PER_CELL = 4\n/** At most this many cells along each side. */\nconst MOST = 1024\n\nconst grids = new WeakMap<Float64Array, { xs: Float64Array; grid: PointGrid }>()\n\n/** The grid of the points `(xs[i], ys[i])`; points with a `NaN` are left out. Cached per data. */\nexport function pointGrid(xs: Float64Array, ys: Float64Array): PointGrid {\n const known = grids.get(ys)\n if (known && known.xs === xs) return known.grid\n const n = Math.min(xs.length, ys.length)\n let x0 = Infinity\n let x1 = -Infinity\n let y0 = Infinity\n let y1 = -Infinity\n let count = 0\n for (let i = 0; i < n; i++) {\n const x = xs[i]!\n const y = ys[i]!\n if (x !== x || y !== y) continue\n count++\n if (x < x0) x0 = x\n if (x > x1) x1 = x\n if (y < y0) y0 = y\n if (y > y1) y1 = y\n }\n const side = Math.min(MOST, Math.max(1, Math.ceil(Math.sqrt(count / PER_CELL))))\n const columns = x1 > x0 ? side : 1\n const rows = y1 > y0 ? side : 1\n const width = (x1 - x0) / columns || 1\n const height = (y1 - y0) / rows || 1\n // each point's cell, then the points sorted by cell (a counting sort)\n const cells = new Int32Array(n)\n const starts = new Int32Array(columns * rows + 1)\n for (let i = 0; i < n; i++) {\n const x = xs[i]!\n const y = ys[i]!\n if (x !== x || y !== y) {\n cells[i] = -1\n continue\n }\n const c = Math.min(columns - 1, Math.floor((x - x0) / width))\n const r = Math.min(rows - 1, Math.floor((y - y0) / height))\n const at = r * columns + c\n cells[i] = at\n starts[at + 1]!++\n }\n for (let k = 1; k < starts.length; k++) starts[k]! += starts[k - 1]!\n const items = new Int32Array(count)\n const fill = starts.slice(0, -1)\n for (let i = 0; i < n; i++) {\n const at = cells[i]!\n if (at >= 0) items[fill[at]!++] = i\n }\n const grid: PointGrid = { x0, y0, columns, rows, width, height, starts, items }\n grids.set(ys, { xs, grid })\n return grid\n}\n\n/** The cell along one side that `value` is in, kept on the grid. */\nconst cell = (value: number, origin: number, size: number, most: number) =>\n Math.min(most - 1, Math.max(0, Math.floor((value - origin) / size)))\n\n/** Calls `visit` with each point in the cells that touch the data box from `(xa, ya)` to `(xb, yb)`. */\nexport function eachNear(\n grid: PointGrid,\n xa: number,\n xb: number,\n ya: number,\n yb: number,\n visit: (index: number) => void,\n) {\n const { x0, y0, columns, rows, width, height, starts, items } = grid\n const c0 = cell(Math.min(xa, xb), x0, width, columns)\n const c1 = cell(Math.max(xa, xb), x0, width, columns)\n const r0 = cell(Math.min(ya, yb), y0, height, rows)\n const r1 = cell(Math.max(ya, yb), y0, height, rows)\n // a box wholly off the grid's side touches nothing\n if (Math.max(xa, xb) < x0 || Math.min(xa, xb) > x0 + width * columns) return\n if (Math.max(ya, yb) < y0 || Math.min(ya, yb) > y0 + height * rows) return\n for (let r = r0; r <= r1; r++) {\n for (let c = c0; c <= c1; c++) {\n const at = r * columns + c\n for (let k = starts[at]!, end = starts[at + 1]!; k < end; k++) visit(items[k]!)\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,187 @@
1
+ import { extent } from "./data.js";
2
+ // src/core/heatmap.ts
3
+ /** The room between cells, in px: the surface shows through as a grid. */
4
+ var GAP = 1;
5
+ /** Corner radius of a cell, in px. */
6
+ var RADIUS = 2;
7
+ /** How much of the color the smallest value takes, so it still shows on the surface. */
8
+ var LEAST = .06;
9
+ /** The values a heatmap's scale runs between: `min` and `max` as given, else the data's ends. */
10
+ function heatRange(series) {
11
+ const { min, max } = series.props;
12
+ const data = extent(series.y);
13
+ const lo = Number.isFinite(min) ? min : data?.[0];
14
+ const hi = Number.isFinite(max) ? max : data?.[1];
15
+ if (lo === void 0 || hi === void 0) return null;
16
+ return hi > lo ? [lo, hi] : data;
17
+ }
18
+ /** Where `value` falls on the scale, 0 to 1. */
19
+ function heatShare(value, range) {
20
+ const [lo, hi] = range;
21
+ if (!(hi > lo)) return 1;
22
+ return Math.min(1, Math.max(0, (value - lo) / (hi - lo)));
23
+ }
24
+ /** An `rgb()` / `rgba()` / `#rrggbb` color as OKLab, or `null`. */
25
+ function toLab(color) {
26
+ let rgb = null;
27
+ const m = /rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/.exec(color);
28
+ if (m) rgb = [
29
+ Number(m[1]),
30
+ Number(m[2]),
31
+ Number(m[3])
32
+ ];
33
+ else {
34
+ const hex = /^#([\da-f]{6})$/i.exec(color);
35
+ if (hex) {
36
+ const n = Number.parseInt(hex[1], 16);
37
+ rgb = [
38
+ n >> 16 & 255,
39
+ n >> 8 & 255,
40
+ n & 255
41
+ ];
42
+ }
43
+ }
44
+ if (!rgb) return null;
45
+ const [r, g, b] = rgb.map((c) => {
46
+ const v = c / 255;
47
+ return v <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4;
48
+ });
49
+ const l = Math.cbrt(.4122214708 * r + .5363325363 * g + .0514459929 * b);
50
+ const mm = Math.cbrt(.2119034982 * r + .6806995451 * g + .1073969566 * b);
51
+ const s = Math.cbrt(.0883024619 * r + .2817188376 * g + .6299787005 * b);
52
+ return [
53
+ .2104542553 * l + .793617785 * mm - .0040720468 * s,
54
+ 1.9779984951 * l - 2.428592205 * mm + .4505937099 * s,
55
+ .0259040371 * l + .7827717662 * mm - .808675766 * s
56
+ ];
57
+ }
58
+ function fromLab([L, A, B]) {
59
+ const l = (L + .3963377774 * A + .2158037573 * B) ** 3;
60
+ const m = (L - .1055613458 * A - .0638541728 * B) ** 3;
61
+ const s = (L - .0894841775 * A - 1.291485548 * B) ** 3;
62
+ const [r, g, b] = [
63
+ 4.0767416621 * l - 3.3077115913 * m + .2309699292 * s,
64
+ -1.2684380046 * l + 2.6097574011 * m - .3413193965 * s,
65
+ -.0041960863 * l - .7034186147 * m + 1.707614701 * s
66
+ ].map((v) => {
67
+ const c = v <= .0031308 ? 12.92 * v : 1.055 * Math.max(0, v) ** (1 / 2.4) - .055;
68
+ return Math.round(Math.min(1, Math.max(0, c)) * 255);
69
+ });
70
+ return `rgb(${r}, ${g}, ${b})`;
71
+ }
72
+ /**
73
+ * The color at `share` (0 to 1) of a scale from `surface` to `color`,
74
+ * mixed in OKLab so equal steps look equal; the smallest still shows.
75
+ */
76
+ function heatColor(color, surface, share) {
77
+ const to = toLab(color);
78
+ const from = toLab(surface);
79
+ if (!to || !from) return color;
80
+ const t = LEAST + .94 * share;
81
+ return fromLab([
82
+ from[0] + (to[0] - from[0]) * t,
83
+ from[1] + (to[1] - from[1]) * t,
84
+ from[2] + (to[2] - from[2]) * t
85
+ ]);
86
+ }
87
+ /** A cell's box: its center and size in px, whichever way the chart runs. */
88
+ function cellOf(series, view, i) {
89
+ const x = series.x.values[i];
90
+ const y = series.top[i];
91
+ if (x !== x || y !== y || series.y[i] !== series.y[i]) return null;
92
+ const [cx, cy] = view.point(x, y);
93
+ const across = view.band;
94
+ const down = Math.abs(view.y.map(0) - view.y.map(1));
95
+ const [width, height] = view.horizontal ? [down, across] : [across, down];
96
+ return {
97
+ x: cx - width / 2,
98
+ y: cy - height / 2,
99
+ width,
100
+ height,
101
+ cx,
102
+ cy
103
+ };
104
+ }
105
+ /** The colors of a series' cells, by row, worked out once per draw. */
106
+ function cellColors(series, view) {
107
+ const range = heatRange(series);
108
+ const out = Array.from({ length: series.y.length });
109
+ if (!range) return out;
110
+ const steps = /* @__PURE__ */ new Map();
111
+ for (let i = 0; i < series.y.length; i++) {
112
+ const v = series.y[i];
113
+ if (v !== v) continue;
114
+ const step = Math.round(heatShare(v, range) * 255);
115
+ let c = steps.get(step);
116
+ if (!c) steps.set(step, c = heatColor(series.color, view.colors.surface, step / 255));
117
+ out[i] = c;
118
+ }
119
+ return out;
120
+ }
121
+ var heatmapKind = {
122
+ type: "heatmap",
123
+ categoryY: true,
124
+ band: true,
125
+ tooltip: "item",
126
+ draw(painter, series, view) {
127
+ const colors = cellColors(series, view);
128
+ const { plot } = view;
129
+ for (let i = 0; i < series.y.length; i++) {
130
+ const cell = cellOf(series, view, i);
131
+ const color = colors[i];
132
+ if (!cell || !color) continue;
133
+ if (cell.x + cell.width < plot.x || cell.x > plot.x + plot.width) continue;
134
+ const w = Math.max(0, cell.width - GAP);
135
+ const h = Math.max(0, cell.height - GAP);
136
+ const r = Math.min(RADIUS, w / 4, h / 4);
137
+ painter.fillStyle = color;
138
+ painter.beginPath();
139
+ roundRect(painter, cell.x + GAP / 2, cell.y + GAP / 2, w, h, r);
140
+ painter.fill();
141
+ }
142
+ },
143
+ hit(series, view, px, py) {
144
+ for (let i = 0; i < series.y.length; i++) {
145
+ const cell = cellOf(series, view, i);
146
+ if (!cell) continue;
147
+ if (px >= cell.x && px < cell.x + cell.width && py >= cell.y && py < cell.y + cell.height) return {
148
+ index: i,
149
+ radius: 0
150
+ };
151
+ }
152
+ return null;
153
+ },
154
+ labels(series, view) {
155
+ const colors = cellColors(series, view);
156
+ const out = [];
157
+ for (let i = 0; i < series.y.length; i++) {
158
+ const cell = cellOf(series, view, i);
159
+ const color = colors[i];
160
+ if (!cell || !color) continue;
161
+ out.push({
162
+ index: i,
163
+ at: [cell.cx, cell.cy],
164
+ side: "center",
165
+ box: {
166
+ x: cell.x,
167
+ y: cell.y,
168
+ width: cell.width - GAP,
169
+ height: cell.height - GAP,
170
+ color
171
+ }
172
+ });
173
+ }
174
+ return out;
175
+ }
176
+ };
177
+ function roundRect(p, x, y, w, h, r) {
178
+ p.moveTo(x + r, y);
179
+ p.arcTo(x + w, y, x + w, y + h, r);
180
+ p.arcTo(x + w, y + h, x, y + h, r);
181
+ p.arcTo(x, y + h, x, y, r);
182
+ p.arcTo(x, y, x + w, y, r);
183
+ p.closePath();
184
+ }
185
+ export { heatColor, heatRange, heatShare, heatmapKind };
186
+
187
+ //# sourceMappingURL=heatmap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heatmap.js","names":[],"sources":["../../src/core/heatmap.ts"],"sourcesContent":["import { extent } from './data'\nimport type { Painter } from './painter'\nimport type { ChartHit, ChartLabelAnchor, ChartSeries, ChartSeriesKind, ChartView } from './series'\n\n/*\n * A heatmap: a cell for each row, at its x and the category its `y`\n * names, colored by its value along a sequential scale from the chart's\n * surface to the series' color. Lighter is less, darker (or stronger) is\n * more, in steps the eye reads as even.\n */\n\nexport interface HeatmapStyle {\n /** The value the scale starts at; by default the smallest. */\n min?: number\n /** The value the scale ends at; by default the largest. */\n max?: number\n}\n\n/** The room between cells, in px: the surface shows through as a grid. */\nconst GAP = 1\n/** Corner radius of a cell, in px. */\nconst RADIUS = 2\n/** How much of the color the smallest value takes, so it still shows on the surface. */\nconst LEAST = 0.06\n\n/** The values a heatmap's scale runs between: `min` and `max` as given, else the data's ends. */\nexport function heatRange(\n series: Pick<ChartSeries<HeatmapStyle>, 'props' | 'y'>,\n): [number, number] | null {\n const { min, max } = series.props\n const data = extent(series.y)\n const lo = Number.isFinite(min) ? min! : data?.[0]\n const hi = Number.isFinite(max) ? max! : data?.[1]\n if (lo === undefined || hi === undefined) return null\n return hi > lo ? [lo, hi] : data\n}\n\n/** Where `value` falls on the scale, 0 to 1. */\nexport function heatShare(value: number, range: [number, number]): number {\n const [lo, hi] = range\n if (!(hi > lo)) return 1\n return Math.min(1, Math.max(0, (value - lo) / (hi - lo)))\n}\n\ntype Lab = [number, number, number]\n\n/** An `rgb()` / `rgba()` / `#rrggbb` color as OKLab, or `null`. */\nfunction toLab(color: string): Lab | null {\n let rgb: number[] | null = null\n const m = /rgba?\\(\\s*([\\d.]+)[\\s,]+([\\d.]+)[\\s,]+([\\d.]+)/.exec(color)\n if (m) rgb = [Number(m[1]), Number(m[2]), Number(m[3])]\n else {\n const hex = /^#([\\da-f]{6})$/i.exec(color)\n if (hex) {\n const n = Number.parseInt(hex[1]!, 16)\n rgb = [(n >> 16) & 255, (n >> 8) & 255, n & 255]\n }\n }\n if (!rgb) return null\n const [r, g, b] = rgb.map((c) => {\n const v = c / 255\n return v <= 0.040_45 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4\n }) as [number, number, number]\n const l = Math.cbrt(0.412_221_470_8 * r + 0.536_332_536_3 * g + 0.051_445_992_9 * b)\n const mm = Math.cbrt(0.211_903_498_2 * r + 0.680_699_545_1 * g + 0.107_396_956_6 * b)\n const s = Math.cbrt(0.088_302_461_9 * r + 0.281_718_837_6 * g + 0.629_978_700_5 * b)\n return [\n 0.210_454_255_3 * l + 0.793_617_785 * mm - 0.004_072_046_8 * s,\n 1.977_998_495_1 * l - 2.428_592_205 * mm + 0.450_593_709_9 * s,\n 0.025_904_037_1 * l + 0.782_771_766_2 * mm - 0.808_675_766 * s,\n ]\n}\n\nfunction fromLab([L, A, B]: Lab): string {\n const l = (L + 0.396_337_777_4 * A + 0.215_803_757_3 * B) ** 3\n const m = (L - 0.105_561_345_8 * A - 0.063_854_172_8 * B) ** 3\n const s = (L - 0.089_484_177_5 * A - 1.291_485_548 * B) ** 3\n const linear = [\n 4.076_741_662_1 * l - 3.307_711_591_3 * m + 0.230_969_929_2 * s,\n -1.268_438_004_6 * l + 2.609_757_401_1 * m - 0.341_319_396_5 * s,\n -0.004_196_086_3 * l - 0.703_418_614_7 * m + 1.707_614_701 * s,\n ]\n const [r, g, b] = linear.map((v) => {\n const c = v <= 0.003_130_8 ? 12.92 * v : 1.055 * Math.max(0, v) ** (1 / 2.4) - 0.055\n return Math.round(Math.min(1, Math.max(0, c)) * 255)\n })\n return `rgb(${r}, ${g}, ${b})`\n}\n\n/**\n * The color at `share` (0 to 1) of a scale from `surface` to `color`,\n * mixed in OKLab so equal steps look equal; the smallest still shows.\n */\nexport function heatColor(color: string, surface: string, share: number): string {\n const to = toLab(color)\n const from = toLab(surface)\n if (!to || !from) return color\n const t = LEAST + (1 - LEAST) * share\n return fromLab([\n from[0] + (to[0] - from[0]) * t,\n from[1] + (to[1] - from[1]) * t,\n from[2] + (to[2] - from[2]) * t,\n ])\n}\n\n/** A cell's box: its center and size in px, whichever way the chart runs. */\nfunction cellOf(series: ChartSeries<HeatmapStyle>, view: ChartView, i: number) {\n const x = series.x.values[i]!\n const y = series.top[i]!\n if (x !== x || y !== y || series.y[i] !== series.y[i]) return null\n const [cx, cy] = view.point(x, y)\n const across = view.band\n const down = Math.abs(view.y.map(0) - view.y.map(1))\n const [width, height] = view.horizontal ? [down, across] : [across, down]\n return { x: cx - width / 2, y: cy - height / 2, width, height, cx, cy }\n}\n\n/** The colors of a series' cells, by row, worked out once per draw. */\nfunction cellColors(series: ChartSeries<HeatmapStyle>, view: ChartView): string[] {\n const range = heatRange(series)\n const out: string[] = Array.from({ length: series.y.length })\n if (!range) return out\n // a handful of colors are used over and over: one per step of the scale\n const steps = new Map<number, string>()\n for (let i = 0; i < series.y.length; i++) {\n const v = series.y[i]!\n if (v !== v) continue\n const step = Math.round(heatShare(v, range) * 255)\n let c = steps.get(step)\n if (!c) steps.set(step, (c = heatColor(series.color, view.colors.surface, step / 255)))\n out[i] = c\n }\n return out\n}\n\nexport const heatmapKind: ChartSeriesKind<HeatmapStyle> = {\n type: 'heatmap',\n categoryY: true,\n band: true,\n tooltip: 'item',\n draw(painter: Painter, series, view) {\n const colors = cellColors(series, view)\n const { plot } = view\n for (let i = 0; i < series.y.length; i++) {\n const cell = cellOf(series, view, i)\n const color = colors[i]\n if (!cell || !color) continue\n if (cell.x + cell.width < plot.x || cell.x > plot.x + plot.width) continue\n const w = Math.max(0, cell.width - GAP)\n const h = Math.max(0, cell.height - GAP)\n const r = Math.min(RADIUS, w / 4, h / 4)\n painter.fillStyle = color\n painter.beginPath()\n roundRect(painter, cell.x + GAP / 2, cell.y + GAP / 2, w, h, r)\n painter.fill()\n }\n },\n hit(series, view, px, py): ChartHit | null {\n for (let i = 0; i < series.y.length; i++) {\n const cell = cellOf(series, view, i)\n if (!cell) continue\n if (px >= cell.x && px < cell.x + cell.width && py >= cell.y && py < cell.y + cell.height)\n return { index: i, radius: 0 }\n }\n return null\n },\n labels(series, view): ChartLabelAnchor[] {\n const colors = cellColors(series, view)\n const out: ChartLabelAnchor[] = []\n for (let i = 0; i < series.y.length; i++) {\n const cell = cellOf(series, view, i)\n const color = colors[i]\n if (!cell || !color) continue\n out.push({\n index: i,\n at: [cell.cx, cell.cy],\n side: 'center',\n box: { x: cell.x, y: cell.y, width: cell.width - GAP, height: cell.height - GAP, color },\n })\n }\n return out\n },\n}\n\nfunction roundRect(p: Painter, x: number, y: number, w: number, h: number, r: number) {\n p.moveTo(x + r, y)\n p.arcTo(x + w, y, x + w, y + h, r)\n p.arcTo(x + w, y + h, x, y + h, r)\n p.arcTo(x, y + h, x, y, r)\n p.arcTo(x, y, x + w, y, r)\n p.closePath()\n}\n"],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ import { nextNiceStep, niceStep } from "./scale.js";
2
+ import { barLabels, drawBars } from "./bar.js";
3
+ import { warnOnce } from "./warn.js";
4
+ // src/core/histogram.ts
5
+ /** The most bins a histogram has: more would be thinner than a pixel, and slow. */
6
+ var MAX_BINS = 1e3;
7
+ /**
8
+ * Counts `values` into bins of width `step`, or of a round width giving
9
+ * about `count` bins (by default, by the number of values: Sturges' rule).
10
+ * Values that aren't numbers are left out.
11
+ */
12
+ function histogramBins(values, { count, step } = {}) {
13
+ let min = Infinity;
14
+ let max = -Infinity;
15
+ let n = 0;
16
+ for (let i = 0; i < values.length; i++) {
17
+ const v = values[i];
18
+ if (!Number.isFinite(v)) continue;
19
+ n++;
20
+ if (v < min) min = v;
21
+ if (v > max) max = v;
22
+ }
23
+ if (!n) return [];
24
+ if (step !== void 0 && !(step > 0)) warnOnce("histogram-step", `a histogram's \`step\` must be above 0; ${step} is left out.`);
25
+ let width = step && step > 0 ? step : max > min ? niceStep(max - min, Math.min(MAX_BINS, Math.max(1, count ?? Math.ceil(Math.log2(n)) + 1))) : 1;
26
+ if ((max - Math.floor(min / width) * width) / width > 1e3) {
27
+ warnOnce("histogram-bins", `a histogram's \`step\` of ${width} makes more than ${MAX_BINS} bins; they are widened.`);
28
+ width = niceStep(max - min, MAX_BINS / 2);
29
+ while ((max - Math.floor(min / width) * width) / width > MAX_BINS) width = nextNiceStep(width);
30
+ }
31
+ const start = Math.floor(min / width) * width;
32
+ const bins = Math.max(1, Math.ceil((max - start) / width - 1e-9));
33
+ const out = [];
34
+ for (let k = 0; k < bins; k++) {
35
+ const x0 = round(start + k * width, width);
36
+ const x1 = round(start + (k + 1) * width, width);
37
+ out.push({
38
+ x0,
39
+ x1,
40
+ x: (x0 + x1) / 2,
41
+ count: 0
42
+ });
43
+ }
44
+ for (let i = 0; i < values.length; i++) {
45
+ const v = values[i];
46
+ if (!Number.isFinite(v)) continue;
47
+ const k = Math.min(bins - 1, Math.floor((v - start) / width + 1e-9));
48
+ out[k].count++;
49
+ }
50
+ return out;
51
+ }
52
+ function round(value, step) {
53
+ const digits = Math.max(0, -Math.floor(Math.log10(step)) + 1);
54
+ return Number(value.toFixed(Math.min(20, digits)));
55
+ }
56
+ /** The bars across all of each bin, a hairline apart. */
57
+ var placement = (view) => {
58
+ const gap = view.band > 4 ? 1 : 0;
59
+ return {
60
+ offset: -view.band / 2 + gap / 2,
61
+ width: view.band - gap
62
+ };
63
+ };
64
+ var histogramKind = {
65
+ type: "bar",
66
+ zero: true,
67
+ band: true,
68
+ draw: (painter, series, view) => drawBars(painter, series, view, placement(view)),
69
+ labels: (series, view) => barLabels(series, view, placement(view)),
70
+ xTitle: (series, row, text) => {
71
+ const b = series.rows[row];
72
+ return b ? `${text(b.x0)} – ${text(b.x1)}` : "";
73
+ },
74
+ xStep: (series) => {
75
+ const b = series.rows[0];
76
+ return b ? b.x1 - b.x0 : NaN;
77
+ }
78
+ };
79
+ export { MAX_BINS, histogramBins, histogramKind };
80
+
81
+ //# sourceMappingURL=histogram.js.map