@perspective-dev/viewer-charts 4.3.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 (258) hide show
  1. package/LICENSE.md +193 -0
  2. package/dist/cdn/perspective-viewer-charts.js +3 -0
  3. package/dist/cdn/perspective-viewer-charts.js.map +7 -0
  4. package/dist/esm/axis/axis-primitives.d.ts +24 -0
  5. package/dist/esm/axis/bar-axis.d.ts +51 -0
  6. package/dist/esm/axis/canvas.d.ts +24 -0
  7. package/dist/esm/axis/categorical-axis-core.d.ts +42 -0
  8. package/dist/esm/axis/categorical-axis.d.ts +27 -0
  9. package/dist/esm/axis/facet-chrome.d.ts +13 -0
  10. package/dist/esm/axis/label-geometry.d.ts +41 -0
  11. package/dist/esm/axis/legend.d.ts +44 -0
  12. package/dist/esm/axis/numeric-axis.d.ts +20 -0
  13. package/dist/esm/charts/candlestick/candlestick-build.d.ts +129 -0
  14. package/dist/esm/charts/candlestick/candlestick-interact.d.ts +10 -0
  15. package/dist/esm/charts/candlestick/candlestick-render.d.ts +24 -0
  16. package/dist/esm/charts/candlestick/candlestick.d.ts +144 -0
  17. package/dist/esm/charts/candlestick/glyphs/draw-candlesticks.d.ts +36 -0
  18. package/dist/esm/charts/candlestick/glyphs/draw-ohlc.d.ts +33 -0
  19. package/dist/esm/charts/canvas-types.d.ts +15 -0
  20. package/dist/esm/charts/cartesian/cartesian-build.d.ts +14 -0
  21. package/dist/esm/charts/cartesian/cartesian-interact.d.ts +20 -0
  22. package/dist/esm/charts/cartesian/cartesian-render.d.ts +26 -0
  23. package/dist/esm/charts/cartesian/cartesian.d.ts +239 -0
  24. package/dist/esm/charts/cartesian/glyph.d.ts +53 -0
  25. package/dist/esm/charts/cartesian/glyphs/density.d.ts +142 -0
  26. package/dist/esm/charts/cartesian/glyphs/lines.d.ts +23 -0
  27. package/dist/esm/charts/cartesian/glyphs/points.d.ts +24 -0
  28. package/dist/esm/charts/cartesian/label-interner.d.ts +21 -0
  29. package/dist/esm/charts/cartesian/tooltip-lines.d.ts +11 -0
  30. package/dist/esm/charts/chart-base.d.ts +402 -0
  31. package/dist/esm/charts/chart.d.ts +338 -0
  32. package/dist/esm/charts/common/band-layout.d.ts +32 -0
  33. package/dist/esm/charts/common/categorical-y-chart.d.ts +53 -0
  34. package/dist/esm/charts/common/category-axis-resolver.d.ts +90 -0
  35. package/dist/esm/charts/common/chrome-cache.d.ts +18 -0
  36. package/dist/esm/charts/common/draw-tooltip-box.d.ts +9 -0
  37. package/dist/esm/charts/common/leaf-color.d.ts +33 -0
  38. package/dist/esm/charts/common/node-store.d.ts +81 -0
  39. package/dist/esm/charts/common/tree-chart.d.ts +48 -0
  40. package/dist/esm/charts/common/tree-chrome.d.ts +31 -0
  41. package/dist/esm/charts/common/tree-data.d.ts +54 -0
  42. package/dist/esm/charts/common/visible-extent.d.ts +51 -0
  43. package/dist/esm/charts/heatmap/heatmap-build.d.ts +86 -0
  44. package/dist/esm/charts/heatmap/heatmap-interact.d.ts +19 -0
  45. package/dist/esm/charts/heatmap/heatmap-render.d.ts +19 -0
  46. package/dist/esm/charts/heatmap/heatmap-y-axis.d.ts +46 -0
  47. package/dist/esm/charts/heatmap/heatmap.d.ts +117 -0
  48. package/dist/esm/charts/map/map.d.ts +67 -0
  49. package/dist/esm/charts/registry.d.ts +14 -0
  50. package/dist/esm/charts/series/glyphs/draw-areas.d.ts +30 -0
  51. package/dist/esm/charts/series/glyphs/draw-bars.d.ts +15 -0
  52. package/dist/esm/charts/series/glyphs/draw-lines.d.ts +34 -0
  53. package/dist/esm/charts/series/glyphs/draw-scatter.d.ts +33 -0
  54. package/dist/esm/charts/series/series-build.d.ts +228 -0
  55. package/dist/esm/charts/series/series-interact.d.ts +35 -0
  56. package/dist/esm/charts/series/series-render.d.ts +41 -0
  57. package/dist/esm/charts/series/series-type.d.ts +49 -0
  58. package/dist/esm/charts/series/series.d.ts +317 -0
  59. package/dist/esm/charts/sunburst/sunburst-interact.d.ts +7 -0
  60. package/dist/esm/charts/sunburst/sunburst-layout.d.ts +33 -0
  61. package/dist/esm/charts/sunburst/sunburst-render.d.ts +22 -0
  62. package/dist/esm/charts/sunburst/sunburst.d.ts +85 -0
  63. package/dist/esm/charts/treemap/treemap-interact.d.ts +12 -0
  64. package/dist/esm/charts/treemap/treemap-layout.d.ts +28 -0
  65. package/dist/esm/charts/treemap/treemap-render.d.ts +18 -0
  66. package/dist/esm/charts/treemap/treemap.d.ts +74 -0
  67. package/dist/esm/config.d.ts +27 -0
  68. package/dist/esm/data/lazy-row.d.ts +32 -0
  69. package/dist/esm/data/split-groups.d.ts +20 -0
  70. package/dist/esm/data/view-reader.d.ts +35 -0
  71. package/dist/esm/event-detail.d.ts +28 -0
  72. package/dist/esm/index.d.ts +3 -0
  73. package/dist/esm/interaction/hit-test.d.ts +30 -0
  74. package/dist/esm/interaction/host-sink-dom.d.ts +19 -0
  75. package/dist/esm/interaction/host-sink-message.d.ts +46 -0
  76. package/dist/esm/interaction/lazy-tooltip.d.ts +61 -0
  77. package/dist/esm/interaction/raw-event-forwarder.d.ts +27 -0
  78. package/dist/esm/interaction/spatial-grid.d.ts +15 -0
  79. package/dist/esm/interaction/tooltip-controller.d.ts +193 -0
  80. package/dist/esm/interaction/zoom-controller.d.ts +106 -0
  81. package/dist/esm/interaction/zoom-router.d.ts +48 -0
  82. package/dist/esm/layout/facet-grid.d.ts +126 -0
  83. package/dist/esm/layout/plot-layout.d.ts +104 -0
  84. package/dist/esm/layout/ticks.d.ts +17 -0
  85. package/dist/esm/map/mercator.d.ts +102 -0
  86. package/dist/esm/map/tile-cache.d.ts +38 -0
  87. package/dist/esm/map/tile-layer.d.ts +66 -0
  88. package/dist/esm/map/tile-loader.d.ts +52 -0
  89. package/dist/esm/map/tile-source.d.ts +66 -0
  90. package/dist/esm/perspective-viewer-charts.js +3 -0
  91. package/dist/esm/perspective-viewer-charts.js.map +7 -0
  92. package/dist/esm/plugin/charts.d.ts +40 -0
  93. package/dist/esm/plugin/plugin.d.ts +95 -0
  94. package/dist/esm/render/scheduler.d.ts +41 -0
  95. package/dist/esm/theme/gradient.d.ts +48 -0
  96. package/dist/esm/theme/palette.d.ts +13 -0
  97. package/dist/esm/theme/theme-snapshot.d.ts +7 -0
  98. package/dist/esm/theme/theme.d.ts +53 -0
  99. package/dist/esm/transport/protocol.d.ts +430 -0
  100. package/dist/esm/transport/renderer-transport.d.ts +201 -0
  101. package/dist/esm/utils/css.d.ts +1 -0
  102. package/dist/esm/utils/font-snapshot.d.ts +50 -0
  103. package/dist/esm/webgl/buffer-pool.d.ts +62 -0
  104. package/dist/esm/webgl/context-manager.d.ts +184 -0
  105. package/dist/esm/webgl/gradient-texture.d.ts +17 -0
  106. package/dist/esm/webgl/instanced-attrs.d.ts +44 -0
  107. package/dist/esm/webgl/plot-frame.d.ts +39 -0
  108. package/dist/esm/webgl/program-cache.d.ts +13 -0
  109. package/dist/esm/webgl/shader-manifest.d.ts +53 -0
  110. package/dist/esm/webgl/shader-registry.d.ts +22 -0
  111. package/dist/esm/worker/boot.d.ts +0 -0
  112. package/dist/esm/worker/dispatch.d.ts +9 -0
  113. package/dist/esm/worker/font-loader.d.ts +2 -0
  114. package/dist/esm/worker/renderer.worker.d.ts +115 -0
  115. package/dist/esm/worker/session-host.d.ts +26 -0
  116. package/package.json +47 -0
  117. package/src/css/perspective-viewer-charts.css +95 -0
  118. package/src/ts/axis/axis-primitives.ts +125 -0
  119. package/src/ts/axis/bar-axis.ts +345 -0
  120. package/src/ts/axis/canvas.ts +64 -0
  121. package/src/ts/axis/categorical-axis-core.ts +125 -0
  122. package/src/ts/axis/categorical-axis.ts +716 -0
  123. package/src/ts/axis/facet-chrome.ts +42 -0
  124. package/src/ts/axis/label-geometry.ts +188 -0
  125. package/src/ts/axis/legend.ts +218 -0
  126. package/src/ts/axis/numeric-axis.ts +353 -0
  127. package/src/ts/charts/candlestick/candlestick-build.ts +516 -0
  128. package/src/ts/charts/candlestick/candlestick-interact.ts +256 -0
  129. package/src/ts/charts/candlestick/candlestick-render.ts +387 -0
  130. package/src/ts/charts/candlestick/candlestick.ts +367 -0
  131. package/src/ts/charts/candlestick/glyphs/draw-candlesticks.ts +432 -0
  132. package/src/ts/charts/candlestick/glyphs/draw-ohlc.ts +317 -0
  133. package/src/ts/charts/canvas-types.ts +30 -0
  134. package/src/ts/charts/cartesian/cartesian-build.ts +616 -0
  135. package/src/ts/charts/cartesian/cartesian-interact.ts +355 -0
  136. package/src/ts/charts/cartesian/cartesian-render.ts +948 -0
  137. package/src/ts/charts/cartesian/cartesian.ts +469 -0
  138. package/src/ts/charts/cartesian/glyph.ts +81 -0
  139. package/src/ts/charts/cartesian/glyphs/density.ts +1263 -0
  140. package/src/ts/charts/cartesian/glyphs/lines.ts +320 -0
  141. package/src/ts/charts/cartesian/glyphs/points.ts +239 -0
  142. package/src/ts/charts/cartesian/label-interner.ts +56 -0
  143. package/src/ts/charts/cartesian/tooltip-lines.ts +80 -0
  144. package/src/ts/charts/chart-base.ts +840 -0
  145. package/src/ts/charts/chart.ts +427 -0
  146. package/src/ts/charts/common/band-layout.ts +63 -0
  147. package/src/ts/charts/common/categorical-y-chart.ts +81 -0
  148. package/src/ts/charts/common/category-axis-resolver.ts +314 -0
  149. package/src/ts/charts/common/chrome-cache.ts +79 -0
  150. package/src/ts/charts/common/draw-tooltip-box.ts +84 -0
  151. package/src/ts/charts/common/leaf-color.ts +92 -0
  152. package/src/ts/charts/common/node-store.ts +235 -0
  153. package/src/ts/charts/common/tree-chart.ts +76 -0
  154. package/src/ts/charts/common/tree-chrome.ts +123 -0
  155. package/src/ts/charts/common/tree-data.ts +623 -0
  156. package/src/ts/charts/common/visible-extent.ts +112 -0
  157. package/src/ts/charts/heatmap/heatmap-build.ts +426 -0
  158. package/src/ts/charts/heatmap/heatmap-interact.ts +274 -0
  159. package/src/ts/charts/heatmap/heatmap-render.ts +815 -0
  160. package/src/ts/charts/heatmap/heatmap-y-axis.ts +351 -0
  161. package/src/ts/charts/heatmap/heatmap.ts +368 -0
  162. package/src/ts/charts/map/map.ts +201 -0
  163. package/src/ts/charts/registry.ts +65 -0
  164. package/src/ts/charts/series/glyphs/draw-areas.ts +331 -0
  165. package/src/ts/charts/series/glyphs/draw-bars.ts +113 -0
  166. package/src/ts/charts/series/glyphs/draw-lines.ts +320 -0
  167. package/src/ts/charts/series/glyphs/draw-scatter.ts +328 -0
  168. package/src/ts/charts/series/series-build.ts +848 -0
  169. package/src/ts/charts/series/series-interact.ts +604 -0
  170. package/src/ts/charts/series/series-render.ts +1109 -0
  171. package/src/ts/charts/series/series-type.ts +99 -0
  172. package/src/ts/charts/series/series.ts +794 -0
  173. package/src/ts/charts/sunburst/sunburst-interact.ts +460 -0
  174. package/src/ts/charts/sunburst/sunburst-layout.ts +238 -0
  175. package/src/ts/charts/sunburst/sunburst-render.ts +887 -0
  176. package/src/ts/charts/sunburst/sunburst.ts +248 -0
  177. package/src/ts/charts/treemap/treemap-interact.ts +445 -0
  178. package/src/ts/charts/treemap/treemap-layout.ts +328 -0
  179. package/src/ts/charts/treemap/treemap-render.ts +886 -0
  180. package/src/ts/charts/treemap/treemap.ts +247 -0
  181. package/src/ts/config.ts +41 -0
  182. package/src/ts/data/lazy-row.ts +140 -0
  183. package/src/ts/data/split-groups.ts +97 -0
  184. package/src/ts/data/view-reader.ts +107 -0
  185. package/src/ts/event-detail.ts +44 -0
  186. package/src/ts/index.ts +53 -0
  187. package/src/ts/interaction/hit-test.ts +106 -0
  188. package/src/ts/interaction/host-sink-dom.ts +85 -0
  189. package/src/ts/interaction/host-sink-message.ts +75 -0
  190. package/src/ts/interaction/lazy-tooltip.ts +102 -0
  191. package/src/ts/interaction/raw-event-forwarder.ts +175 -0
  192. package/src/ts/interaction/spatial-grid.ts +100 -0
  193. package/src/ts/interaction/tooltip-controller.ts +407 -0
  194. package/src/ts/interaction/zoom-controller.ts +468 -0
  195. package/src/ts/interaction/zoom-router.ts +230 -0
  196. package/src/ts/layout/facet-grid.ts +346 -0
  197. package/src/ts/layout/plot-layout.ts +277 -0
  198. package/src/ts/layout/ticks.ts +168 -0
  199. package/src/ts/map/mercator.ts +204 -0
  200. package/src/ts/map/tile-cache.ts +96 -0
  201. package/src/ts/map/tile-layer.ts +382 -0
  202. package/src/ts/map/tile-loader.ts +143 -0
  203. package/src/ts/map/tile-source.ts +156 -0
  204. package/src/ts/plugin/charts.ts +286 -0
  205. package/src/ts/plugin/plugin.ts +668 -0
  206. package/src/ts/render/scheduler.ts +339 -0
  207. package/src/ts/shaders/area.frag.glsl +20 -0
  208. package/src/ts/shaders/area.vert.glsl +19 -0
  209. package/src/ts/shaders/bar.frag.glsl +25 -0
  210. package/src/ts/shaders/bar.vert.glsl +60 -0
  211. package/src/ts/shaders/candlestick-body.frag.glsl +19 -0
  212. package/src/ts/shaders/candlestick-body.vert.glsl +34 -0
  213. package/src/ts/shaders/density-extreme.frag.glsl +30 -0
  214. package/src/ts/shaders/density-mrt.frag.glsl +44 -0
  215. package/src/ts/shaders/density-mrt.vert.glsl +48 -0
  216. package/src/ts/shaders/density-resolve.frag.glsl +89 -0
  217. package/src/ts/shaders/density-resolve.vert.glsl +23 -0
  218. package/src/ts/shaders/density-splat.frag.glsl +34 -0
  219. package/src/ts/shaders/density-splat.vert.glsl +52 -0
  220. package/src/ts/shaders/gridline.frag.glsl +18 -0
  221. package/src/ts/shaders/gridline.vert.glsl +18 -0
  222. package/src/ts/shaders/heatmap.frag.glsl +23 -0
  223. package/src/ts/shaders/heatmap.vert.glsl +42 -0
  224. package/src/ts/shaders/line-uniform.frag.glsl +26 -0
  225. package/src/ts/shaders/line-uniform.vert.glsl +54 -0
  226. package/src/ts/shaders/line.frag.glsl +28 -0
  227. package/src/ts/shaders/line.vert.glsl +87 -0
  228. package/src/ts/shaders/scatter.frag.glsl +39 -0
  229. package/src/ts/shaders/scatter.vert.glsl +67 -0
  230. package/src/ts/shaders/sunburst-arc.frag.glsl +19 -0
  231. package/src/ts/shaders/sunburst-arc.vert.glsl +79 -0
  232. package/src/ts/shaders/tile.frag.glsl +27 -0
  233. package/src/ts/shaders/tile.vert.glsl +35 -0
  234. package/src/ts/shaders/treemap.frag.glsl +19 -0
  235. package/src/ts/shaders/treemap.vert.glsl +25 -0
  236. package/src/ts/shaders/y-scatter.frag.glsl +30 -0
  237. package/src/ts/shaders/y-scatter.vert.glsl +31 -0
  238. package/src/ts/theme/gradient.ts +312 -0
  239. package/src/ts/theme/palette.ts +64 -0
  240. package/src/ts/theme/theme-snapshot.ts +66 -0
  241. package/src/ts/theme/theme.ts +166 -0
  242. package/src/ts/transport/protocol.ts +497 -0
  243. package/src/ts/transport/renderer-transport.ts +788 -0
  244. package/src/ts/utils/css.ts +36 -0
  245. package/src/ts/utils/font-snapshot.ts +159 -0
  246. package/src/ts/webgl/buffer-pool.ts +163 -0
  247. package/src/ts/webgl/context-manager.ts +414 -0
  248. package/src/ts/webgl/gradient-texture.ts +84 -0
  249. package/src/ts/webgl/instanced-attrs.ts +139 -0
  250. package/src/ts/webgl/plot-frame.ts +91 -0
  251. package/src/ts/webgl/program-cache.ts +46 -0
  252. package/src/ts/webgl/shader-manifest.ts +148 -0
  253. package/src/ts/webgl/shader-registry.ts +97 -0
  254. package/src/ts/worker/boot.ts +22 -0
  255. package/src/ts/worker/dispatch.ts +99 -0
  256. package/src/ts/worker/font-loader.ts +89 -0
  257. package/src/ts/worker/renderer.worker.ts +734 -0
  258. package/src/ts/worker/session-host.ts +118 -0
@@ -0,0 +1,168 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ function niceNum(value: number, round: boolean): number {
14
+ const exp = Math.floor(Math.log10(value));
15
+ const frac = value / Math.pow(10, exp);
16
+ let nice: number;
17
+ if (round) {
18
+ if (frac < 1.5) {
19
+ nice = 1;
20
+ } else if (frac < 3) {
21
+ nice = 2;
22
+ } else if (frac < 7) {
23
+ nice = 5;
24
+ } else {
25
+ nice = 10;
26
+ }
27
+ } else {
28
+ if (frac <= 1) {
29
+ nice = 1;
30
+ } else if (frac <= 2) {
31
+ nice = 2;
32
+ } else if (frac <= 5) {
33
+ nice = 5;
34
+ } else {
35
+ nice = 10;
36
+ }
37
+ }
38
+
39
+ return nice * Math.pow(10, exp);
40
+ }
41
+
42
+ /**
43
+ * Generate an array of "nice" tick values spanning [min, max].
44
+ * @param min - Domain minimum
45
+ * @param max - Domain maximum
46
+ * @param targetCount - Desired number of ticks (approximate)
47
+ */
48
+ export function computeNiceTicks(
49
+ min: number,
50
+ max: number,
51
+ targetCount: number,
52
+ ): number[] {
53
+ if (targetCount < 1) {
54
+ targetCount = 1;
55
+ }
56
+
57
+ const range = niceNum(max - min, false);
58
+ const step = niceNum(range / targetCount, true);
59
+ const tickMin = Math.ceil(min / step) * step;
60
+ const tickMax = Math.floor(max / step) * step;
61
+
62
+ const ticks: number[] = [];
63
+
64
+ // Use epsilon to avoid floating point overshoot
65
+ for (let t = tickMin; t <= tickMax + step * 0.001; t += step) {
66
+ ticks.push(t);
67
+ }
68
+
69
+ return ticks;
70
+ }
71
+
72
+ /**
73
+ * Format a numeric tick value for display.
74
+ * Uses K/M/B suffixes for large numbers, fixed decimals for small.
75
+ */
76
+ export function formatTickValue(val: number): string {
77
+ const abs = Math.abs(val);
78
+ if (abs === 0) {
79
+ return "0";
80
+ }
81
+
82
+ if (abs >= 1e9) {
83
+ return (val / 1e9).toFixed(1) + "B";
84
+ }
85
+
86
+ if (abs >= 1e6) {
87
+ return (val / 1e6).toFixed(1) + "M";
88
+ }
89
+
90
+ if (abs >= 1e3) {
91
+ return (val / 1e3).toFixed(1) + "K";
92
+ }
93
+
94
+ if (Number.isInteger(val)) {
95
+ return val.toString();
96
+ }
97
+
98
+ if (abs >= 1) {
99
+ return val.toFixed(1);
100
+ }
101
+
102
+ return val.toFixed(2);
103
+ }
104
+
105
+ /**
106
+ * Format a timestamp (ms since epoch) as a human-readable date/time label.
107
+ * Adapts precision based on the tick spacing.
108
+ */
109
+ export function formatDateTickValue(val: number, stepMs?: number): string {
110
+ const d = new Date(val);
111
+ if (isNaN(d.getTime())) {
112
+ return formatTickValue(val);
113
+ }
114
+
115
+ // If step is provided, choose precision based on tick interval
116
+ if (stepMs !== undefined && stepMs > 0) {
117
+ const DAY = 86_400_000;
118
+ const HOUR = 3_600_000;
119
+ const MINUTE = 60_000;
120
+
121
+ if (stepMs >= DAY * 28) {
122
+ // Monthly or longer — show year-month
123
+ return d.toLocaleDateString(undefined, {
124
+ year: "numeric",
125
+ month: "short",
126
+ });
127
+ }
128
+
129
+ if (stepMs >= DAY) {
130
+ // Daily — show month and day
131
+ return d.toLocaleDateString(undefined, {
132
+ month: "short",
133
+ day: "numeric",
134
+ });
135
+ }
136
+
137
+ if (stepMs >= HOUR) {
138
+ // Hourly
139
+ return d.toLocaleString(undefined, {
140
+ month: "short",
141
+ day: "numeric",
142
+ hour: "numeric",
143
+ });
144
+ }
145
+
146
+ if (stepMs >= MINUTE) {
147
+ // Minutes
148
+ return d.toLocaleTimeString(undefined, {
149
+ hour: "numeric",
150
+ minute: "2-digit",
151
+ });
152
+ }
153
+
154
+ // Sub-minute
155
+ return d.toLocaleTimeString(undefined, {
156
+ hour: "numeric",
157
+ minute: "2-digit",
158
+ second: "2-digit",
159
+ });
160
+ }
161
+
162
+ // Default: show date only
163
+ return d.toLocaleDateString(undefined, {
164
+ year: "numeric",
165
+ month: "short",
166
+ day: "numeric",
167
+ });
168
+ }
@@ -0,0 +1,204 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ /**
14
+ * Web Mercator projection helpers and XYZ tile-pyramid math. Pure
15
+ * functions, no side effects, safe to call from any thread (including
16
+ * the renderer worker).
17
+ *
18
+ * Mercator output is in *meters*, not the normalized [0, 1] form that
19
+ * some libraries use. We feed the meter values straight into the
20
+ * cartesian projection matrix in `plot-layout.ts`, so the upstream
21
+ * domain stays in physical units and per-pixel ground resolution is a
22
+ * straightforward division.
23
+ */
24
+
25
+ /**
26
+ * WGS84 equatorial radius in meters. Matches what every standard tile
27
+ * provider (OSM, CartoDB, Mapbox, ...) uses for Web Mercator.
28
+ */
29
+ export const EARTH_RADIUS_M = 6378137;
30
+
31
+ /**
32
+ * Half the world extent in Mercator meters: π · R ≈ 20037508.34. The
33
+ * full Mercator square is `[-WORLD_HALF, +WORLD_HALF]` on both axes.
34
+ */
35
+ export const WORLD_HALF = Math.PI * EARTH_RADIUS_M;
36
+
37
+ /**
38
+ * Maximum absolute latitude representable in Web Mercator. Beyond this
39
+ * the projection diverges to ±∞; tile providers don't ship tiles
40
+ * outside [-MAX_LAT, +MAX_LAT]. Computed as
41
+ * `atan(sinh(π)) · 180 / π ≈ 85.0511287798°`.
42
+ */
43
+ export const MAX_LAT = 85.0511287798066;
44
+
45
+ /**
46
+ * Project (longitude, latitude) in degrees to Web Mercator meters.
47
+ *
48
+ * Latitudes outside ±MAX_LAT return `[NaN, NaN]` so callers in the
49
+ * cartesian build path (which already has a post-`projectPoint` NaN
50
+ * guard) discard those rows without special-casing.
51
+ */
52
+ export function lonLatToMercator(lon: number, lat: number): [number, number] {
53
+ if (lat > MAX_LAT || lat < -MAX_LAT) {
54
+ return [NaN, NaN];
55
+ }
56
+
57
+ const x = (lon * Math.PI * EARTH_RADIUS_M) / 180;
58
+ const latRad = (lat * Math.PI) / 180;
59
+ const y = EARTH_RADIUS_M * Math.log(Math.tan(Math.PI / 4 + latRad / 2));
60
+ return [x, y];
61
+ }
62
+
63
+ /**
64
+ * Inverse: Mercator meters → (lon, lat) in degrees. Used by tooltip
65
+ * formatting and any UI that surfaces the cursor position to the user.
66
+ */
67
+ export function mercatorToLonLat(x: number, y: number): [number, number] {
68
+ const lon = (x * 180) / (Math.PI * EARTH_RADIUS_M);
69
+ const lat = (Math.atan(Math.exp(y / EARTH_RADIUS_M)) * 360) / Math.PI - 90;
70
+ return [lon, lat];
71
+ }
72
+
73
+ /**
74
+ * A single XYZ tile address.
75
+ */
76
+ export interface TileId {
77
+ z: number;
78
+ x: number;
79
+ y: number;
80
+ }
81
+
82
+ /**
83
+ * Mercator extent in meters of one XYZ tile. Y is in the Mercator
84
+ * convention (north positive), not the tile-pyramid convention (y=0
85
+ * at the top); the conversion is done inside the helper.
86
+ */
87
+ export interface TileExtent {
88
+ xMin: number;
89
+ yMin: number;
90
+ xMax: number;
91
+ yMax: number;
92
+ }
93
+
94
+ /**
95
+ * Return the Mercator-meter bounds of an XYZ tile. Tile (0, 0) at
96
+ * zoom 0 covers `[-WORLD_HALF, +WORLD_HALF]` on both axes — the whole
97
+ * world. Each zoom level subdivides into `2^z × 2^z` equal squares.
98
+ */
99
+ export function tileExtent(z: number, x: number, y: number): TileExtent {
100
+ const n = 1 << z;
101
+ const tileSize = (2 * WORLD_HALF) / n;
102
+ const xMin = -WORLD_HALF + x * tileSize;
103
+ const xMax = xMin + tileSize;
104
+ // Tile y=0 sits at the *top* of the pyramid (north), so flip.
105
+ const yMax = WORLD_HALF - y * tileSize;
106
+ const yMin = yMax - tileSize;
107
+ return { xMin, yMin, xMax, yMax };
108
+ }
109
+
110
+ /**
111
+ * Pick the integer zoom level whose tile pixel resolution best matches
112
+ * the requested Mercator-meters-per-pixel. Snaps to the next coarser
113
+ * level so we never undersample (a finer level would fetch tiles only
114
+ * to scale them down).
115
+ *
116
+ * `targetResolutionMpp` is meters per *device pixel*; the caller
117
+ * computes it as `(domain.xMax - domain.xMin) / plotRect.width`.
118
+ */
119
+ export function pickZoom(
120
+ targetResolutionMpp: number,
121
+ tileSizePx = 256,
122
+ maxZoom = 19,
123
+ ): number {
124
+ if (!isFinite(targetResolutionMpp) || targetResolutionMpp <= 0) {
125
+ return 0;
126
+ }
127
+
128
+ // Resolution at zoom z = (2·WORLD_HALF) / (tileSizePx · 2^z).
129
+ // Solve for z; floor so we stay at the next coarser level when in
130
+ // between two levels.
131
+ const z = Math.log2((2 * WORLD_HALF) / (tileSizePx * targetResolutionMpp));
132
+ return Math.max(0, Math.min(maxZoom, Math.floor(z)));
133
+ }
134
+
135
+ /**
136
+ * Enumerate every visible tile at a single zoom level that intersects
137
+ * the given Mercator extent. Returned in left-to-right, top-to-bottom
138
+ * order so the layer's render loop produces a deterministic draw
139
+ * sequence (helps with debugging tile-load races).
140
+ *
141
+ * Tiles outside the world's `[0, 2^z)` X range are *not* wrapped —
142
+ * antimeridian wraparound is a v2 feature. Callers see a gap when
143
+ * panning past ±180° lon; tiles inside the valid range still render.
144
+ */
145
+ export function tilesForExtent(extent: TileExtent, z: number): TileId[] {
146
+ const n = 1 << z;
147
+ const tileSize = (2 * WORLD_HALF) / n;
148
+
149
+ // Tile X grows east; tile Y grows south. Convert the extent's
150
+ // bounds accordingly.
151
+ const xMinTile = Math.floor((extent.xMin + WORLD_HALF) / tileSize);
152
+ const xMaxTile = Math.floor((extent.xMax + WORLD_HALF) / tileSize);
153
+ const yMinTile = Math.floor((WORLD_HALF - extent.yMax) / tileSize);
154
+ const yMaxTile = Math.floor((WORLD_HALF - extent.yMin) / tileSize);
155
+
156
+ const result: TileId[] = [];
157
+ for (let ty = yMinTile; ty <= yMaxTile; ty++) {
158
+ if (ty < 0 || ty >= n) {
159
+ continue;
160
+ }
161
+
162
+ for (let tx = xMinTile; tx <= xMaxTile; tx++) {
163
+ if (tx < 0 || tx >= n) {
164
+ continue;
165
+ }
166
+
167
+ result.push({ z, x: tx, y: ty });
168
+ }
169
+ }
170
+
171
+ return result;
172
+ }
173
+
174
+ /**
175
+ * Return the parent tile (one zoom level coarser) of the given tile,
176
+ * along with the [0, 1] UV sub-rect that this tile occupies inside
177
+ * its parent. Used by the layer's "draw what we have" fallback while
178
+ * a missing target tile is in-flight — the parent texture is sampled
179
+ * with the sub-rect so the visible region keeps tile-aligned content
180
+ * instead of flashing blank.
181
+ *
182
+ * Returns `null` for zoom-0 tiles (no parent).
183
+ */
184
+ export function parentTile(tile: TileId): {
185
+ parent: TileId;
186
+ uvMin: [number, number];
187
+ uvMax: [number, number];
188
+ } | null {
189
+ if (tile.z <= 0) {
190
+ return null;
191
+ }
192
+
193
+ const parent: TileId = {
194
+ z: tile.z - 1,
195
+ x: tile.x >> 1,
196
+ y: tile.y >> 1,
197
+ };
198
+
199
+ const u = tile.x & 1;
200
+ const v = tile.y & 1;
201
+ const uvMin: [number, number] = [u * 0.5, v * 0.5];
202
+ const uvMax: [number, number] = [uvMin[0] + 0.5, uvMin[1] + 0.5];
203
+ return { parent, uvMin, uvMax };
204
+ }
@@ -0,0 +1,96 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ /**
14
+ * Bounded LRU of `WebGLTexture` keyed by tile cache key (see
15
+ * `tile-loader.tileKey`). Evicts least-recently-touched entries on
16
+ * insert past `capacity`, deleting their GPU textures.
17
+ *
18
+ * Default capacity of 256 is enough to cover a full screen worth of
19
+ * tiles at zoom-1 jumps either side of the current viewport plus the
20
+ * parent fallbacks — at 256×256 RGBA that's ~64 MB of texture memory,
21
+ * which sits comfortably under the 256 MB headroom most browsers
22
+ * grant to a tab.
23
+ */
24
+ export class TileCache {
25
+ private _entries = new Map<string, WebGLTexture>();
26
+ private readonly _capacity: number;
27
+
28
+ constructor(capacity = 256) {
29
+ this._capacity = capacity;
30
+ }
31
+
32
+ /**
33
+ * Fetch a texture by key. Touching an existing entry moves it to
34
+ * the LRU tail so it survives the next eviction sweep.
35
+ */
36
+ get(key: string): WebGLTexture | undefined {
37
+ const tex = this._entries.get(key);
38
+ if (tex !== undefined) {
39
+ // Re-insert to push the entry to the tail.
40
+ this._entries.delete(key);
41
+ this._entries.set(key, tex);
42
+ }
43
+
44
+ return tex;
45
+ }
46
+
47
+ /**
48
+ * Insert a texture under `key`. If the cache is at capacity, evict
49
+ * the oldest entry first (calling `gl.deleteTexture` on its GPU
50
+ * resource).
51
+ */
52
+ set(
53
+ gl: WebGL2RenderingContext | WebGLRenderingContext,
54
+ key: string,
55
+ texture: WebGLTexture,
56
+ ): void {
57
+ if (this._entries.has(key)) {
58
+ const old = this._entries.get(key)!;
59
+ gl.deleteTexture(old);
60
+ this._entries.delete(key);
61
+ }
62
+
63
+ this._entries.set(key, texture);
64
+ while (this._entries.size > this._capacity) {
65
+ const oldestKey = this._entries.keys().next().value;
66
+ if (oldestKey === undefined) {
67
+ break;
68
+ }
69
+
70
+ const tex = this._entries.get(oldestKey)!;
71
+ gl.deleteTexture(tex);
72
+ this._entries.delete(oldestKey);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Whether a key is resident. Used to gate the "kick off a fetch"
78
+ * branch in the layer's render loop.
79
+ */
80
+ has(key: string): boolean {
81
+ return this._entries.has(key);
82
+ }
83
+
84
+ /**
85
+ * Release every texture. Called on chart destroy. Safe to call
86
+ * with a stale `gl` reference (no-op if `deleteTexture` rejects),
87
+ * but in practice the caller passes the still-live worker context.
88
+ */
89
+ dispose(gl: WebGL2RenderingContext | WebGLRenderingContext): void {
90
+ for (const tex of this._entries.values()) {
91
+ gl.deleteTexture(tex);
92
+ }
93
+
94
+ this._entries.clear();
95
+ }
96
+ }