@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,382 @@
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
+ import type { WebGLContextManager } from "../webgl/context-manager";
14
+ import type { PlotLayout } from "../layout/plot-layout";
15
+ import { pickZoom, tilesForExtent, tileExtent, type TileId } from "./mercator";
16
+ import { TileCache } from "./tile-cache";
17
+ import { TileLoader, tileKey } from "./tile-loader";
18
+ import type { TileSource } from "./tile-source";
19
+ import tileVert from "../shaders/tile.vert.glsl";
20
+ import tileFrag from "../shaders/tile.frag.glsl";
21
+
22
+ type GL = WebGL2RenderingContext | WebGLRenderingContext;
23
+
24
+ interface TileProgramCache {
25
+ program: WebGLProgram;
26
+ u_projection: WebGLUniformLocation | null;
27
+ u_extent_min: WebGLUniformLocation | null;
28
+ u_extent_max: WebGLUniformLocation | null;
29
+ u_uv_min: WebGLUniformLocation | null;
30
+ u_uv_max: WebGLUniformLocation | null;
31
+ u_tile: WebGLUniformLocation | null;
32
+ u_alpha: WebGLUniformLocation | null;
33
+ a_corner: number;
34
+ }
35
+
36
+ /**
37
+ * Renders an XYZ raster tile basemap into the chart's webgl canvas
38
+ * inside the plot rect's scissor. Used by `MapChart` from inside
39
+ * `renderInPlotFrame`, before the glyph draw, so chart glyphs (point,
40
+ * line, density) composite naturally on top of the basemap.
41
+ *
42
+ * The layer owns:
43
+ * - The tile shader program (compiled once per WebGL context).
44
+ * - A unit-quad corner buffer (one 2-byte attribute, reused per
45
+ * tile).
46
+ * - A `TileCache` (LRU of `WebGLTexture`).
47
+ * - A `TileLoader` (async fetch + dedup).
48
+ *
49
+ * On each render: pick the integer zoom that matches the requested
50
+ * meters-per-pixel, enumerate visible tiles at that zoom, and draw
51
+ * each one β€” either with the loaded texture or with a parent
52
+ * texture's sub-rect while the target is in flight.
53
+ */
54
+ export class TileLayer {
55
+ private _program: TileProgramCache | null = null;
56
+ private _cornerBuffer: WebGLBuffer | null = null;
57
+ private _cache = new TileCache();
58
+ private _loader = new TileLoader();
59
+ private _source: TileSource | null = null;
60
+ private _alpha = 1.0;
61
+ private _onTileLoad: () => void = () => {};
62
+
63
+ /**
64
+ * Hook the "tile arrived" notification through to the chart's
65
+ * render scheduler. Called once when the layer is constructed
66
+ * by `MapChart`.
67
+ */
68
+ setOnTileLoad(cb: () => void): void {
69
+ this._onTileLoad = cb;
70
+ this._loader.setOnLoad(cb);
71
+ }
72
+
73
+ /**
74
+ * Swap the tile source (e.g. light ↔ dark theme). Drops the cache
75
+ * because the cached textures came from the prior source's URLs.
76
+ */
77
+ setSource(gl: GL, source: TileSource): void {
78
+ if (this._source?.id === source.id) {
79
+ return;
80
+ }
81
+
82
+ this._cache.dispose(gl);
83
+ this._loader.cancelAll();
84
+ this._source = source;
85
+ }
86
+
87
+ setAlpha(alpha: number): void {
88
+ this._alpha = Math.max(0, Math.min(1, alpha));
89
+ }
90
+
91
+ get source(): TileSource | null {
92
+ return this._source;
93
+ }
94
+
95
+ /**
96
+ * Render the basemap for the current visible Mercator extent.
97
+ * Caller is responsible for binding the chart's main framebuffer
98
+ * (the plot-frame scissor is already in place by the time we get
99
+ * here). The same `projection` matrix the glyph draw uses is
100
+ * passed straight through.
101
+ */
102
+ render(
103
+ glManager: WebGLContextManager,
104
+ layout: PlotLayout,
105
+ projection: Float32Array,
106
+ domain: { xMin: number; xMax: number; yMin: number; yMax: number },
107
+ xOrigin: number,
108
+ yOrigin: number,
109
+ ): void {
110
+ const source = this._source;
111
+ if (!source) {
112
+ return;
113
+ }
114
+
115
+ this._ensureProgram(glManager);
116
+ const prog = this._program;
117
+ const cornerBuf = this._cornerBuffer;
118
+ if (!prog || !cornerBuf) {
119
+ return;
120
+ }
121
+
122
+ const gl = glManager.gl;
123
+ const dpr = glManager.dpr;
124
+ const plotWidth = Math.max(1, layout.plotRect.width * dpr);
125
+ const xRange = domain.xMax - domain.xMin;
126
+ if (!isFinite(xRange) || xRange <= 0) {
127
+ return;
128
+ }
129
+
130
+ const mpp = xRange / plotWidth;
131
+ const z = pickZoom(mpp, source.tileSize, source.maxZoom);
132
+ const visible = tilesForExtent(domain, z);
133
+
134
+ // Cancel any in-flight fetches for old zooms / off-screen
135
+ // tiles. We compute the live key set up-front so the loader
136
+ // doesn't keep tickling `requestRender` after the user pans
137
+ // past a slow-loading region.
138
+ const liveKeys = new Set<string>();
139
+ for (const t of visible) {
140
+ liveKeys.add(tileKey(source.id, t.z, t.x, t.y));
141
+ }
142
+
143
+ this._loader.cancelExcept(liveKeys);
144
+
145
+ // Setup the shader program + static unit-quad buffer once per
146
+ // frame. Inside the loop only the per-tile uniforms change.
147
+ gl.useProgram(prog.program);
148
+ gl.uniformMatrix4fv(prog.u_projection, false, projection);
149
+ gl.uniform1f(prog.u_alpha, this._alpha);
150
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerBuf);
151
+ gl.enableVertexAttribArray(prog.a_corner);
152
+ gl.vertexAttribPointer(prog.a_corner, 2, gl.FLOAT, false, 0, 0);
153
+ gl.activeTexture(gl.TEXTURE0);
154
+ gl.uniform1i(prog.u_tile, 0);
155
+
156
+ // Tiles are opaque; use the simplest blend mode so the
157
+ // glyph layer (drawn next in `_fullRender`) lands on top
158
+ // naturally without weird premultiplied tricks.
159
+ const wasBlend = gl.isEnabled(gl.BLEND);
160
+ gl.enable(gl.BLEND);
161
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
162
+
163
+ for (const tile of visible) {
164
+ this._drawTile(gl, prog, source, tile, xOrigin, yOrigin);
165
+ }
166
+
167
+ if (!wasBlend) {
168
+ gl.disable(gl.BLEND);
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Free every GPU resource. Called from `MapChart.destroyInternal`.
174
+ */
175
+ destroy(gl: GL): void {
176
+ this._loader.cancelAll();
177
+ this._cache.dispose(gl);
178
+ if (this._cornerBuffer) {
179
+ gl.deleteBuffer(this._cornerBuffer);
180
+ this._cornerBuffer = null;
181
+ }
182
+
183
+ this._program = null;
184
+ }
185
+
186
+ private _drawTile(
187
+ gl: GL,
188
+ prog: TileProgramCache,
189
+ source: TileSource,
190
+ tile: TileId,
191
+ xOrigin: number,
192
+ yOrigin: number,
193
+ ): void {
194
+ // Subtract the chart's rebase origin so the position matches
195
+ // the convention glyphs use (`_xData = absX - xOrigin`). The
196
+ // shared projection matrix bakes in `xOrigin/yOrigin` and
197
+ // would otherwise shift tiles by `sx*xOrigin` clip units β€”
198
+ // for Mercator-scale origins (~1e7 m), well off-screen.
199
+ const rawExtent = tileExtent(tile.z, tile.x, tile.y);
200
+ const extent = {
201
+ xMin: rawExtent.xMin - xOrigin,
202
+ xMax: rawExtent.xMax - xOrigin,
203
+ yMin: rawExtent.yMin - yOrigin,
204
+ yMax: rawExtent.yMax - yOrigin,
205
+ };
206
+ const key = tileKey(source.id, tile.z, tile.x, tile.y);
207
+
208
+ const tex = this._cache.get(key);
209
+ if (tex) {
210
+ this._issueDraw(gl, prog, tex, extent, [0, 0], [1, 1]);
211
+ return;
212
+ }
213
+
214
+ // Cache miss: kick off async load (idempotent β€” loader dedups).
215
+ // No await β€” we paint a fallback this frame and the chart will
216
+ // re-render when the texture arrives.
217
+ this._kickLoad(gl, source, tile);
218
+
219
+ // Walk up the pyramid up to 6 levels looking for any loaded
220
+ // ancestor. Each level halves the UV sub-rect that the target
221
+ // tile occupies inside the ancestor; the math is direct from
222
+ // the tile coordinate bit-shift, no recursive accumulation.
223
+ for (let dz = 1; dz <= 6; dz++) {
224
+ const az = tile.z - dz;
225
+ if (az < 0) {
226
+ return;
227
+ }
228
+
229
+ const ax = tile.x >> dz;
230
+ const ay = tile.y >> dz;
231
+ const ancestorKey = tileKey(source.id, az, ax, ay);
232
+ const ancestorTex = this._cache.get(ancestorKey);
233
+ if (!ancestorTex) {
234
+ continue;
235
+ }
236
+
237
+ const n = 1 << dz;
238
+ const localX = tile.x - ax * n;
239
+ const localY = tile.y - ay * n;
240
+ const span = 1 / n;
241
+ const uvMin: [number, number] = [localX * span, localY * span];
242
+ const uvMax: [number, number] = [uvMin[0] + span, uvMin[1] + span];
243
+ this._issueDraw(gl, prog, ancestorTex, extent, uvMin, uvMax);
244
+ return;
245
+ }
246
+ }
247
+
248
+ private _issueDraw(
249
+ gl: GL,
250
+ prog: TileProgramCache,
251
+ tex: WebGLTexture,
252
+ extent: { xMin: number; yMin: number; xMax: number; yMax: number },
253
+ uvMin: [number, number],
254
+ uvMax: [number, number],
255
+ ): void {
256
+ gl.bindTexture(gl.TEXTURE_2D, tex);
257
+ gl.uniform2f(prog.u_extent_min, extent.xMin, extent.yMin);
258
+ gl.uniform2f(prog.u_extent_max, extent.xMax, extent.yMax);
259
+ gl.uniform2f(prog.u_uv_min, uvMin[0], uvMin[1]);
260
+ gl.uniform2f(prog.u_uv_max, uvMax[0], uvMax[1]);
261
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
262
+ }
263
+
264
+ private _kickLoad(gl: GL, source: TileSource, tile: TileId): void {
265
+ const key = tileKey(source.id, tile.z, tile.x, tile.y);
266
+ if (this._cache.has(key)) {
267
+ return;
268
+ }
269
+
270
+ this._loader.load(source, tile.z, tile.x, tile.y).then((bmp) => {
271
+ if (!bmp) {
272
+ return;
273
+ }
274
+
275
+ // The chart may have switched sources between launch and
276
+ // resolve; drop the bitmap if so.
277
+ if (this._source?.id !== source.id) {
278
+ bmp.close();
279
+ return;
280
+ }
281
+
282
+ const tex = gl.createTexture();
283
+ if (!tex) {
284
+ bmp.close();
285
+ return;
286
+ }
287
+
288
+ // Anchor the upload to a known texture unit. Without
289
+ // this the upload binds to whatever unit was last
290
+ // active (gradient LUT lives at TEXTURE2, etc.), which
291
+ // is harmless for the upload itself but easy to confuse
292
+ // with the sampling path during debugging.
293
+ gl.activeTexture(gl.TEXTURE0);
294
+ gl.bindTexture(gl.TEXTURE_2D, tex);
295
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
296
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
297
+ gl.texParameteri(
298
+ gl.TEXTURE_2D,
299
+ gl.TEXTURE_WRAP_S,
300
+ gl.CLAMP_TO_EDGE,
301
+ );
302
+ gl.texParameteri(
303
+ gl.TEXTURE_2D,
304
+ gl.TEXTURE_WRAP_T,
305
+ gl.CLAMP_TO_EDGE,
306
+ );
307
+
308
+ // Pin pixel-store flags. Workers and main threads start
309
+ // with the WebGL spec defaults, but other parts of the
310
+ // chart (or future extensions) may flip
311
+ // `UNPACK_PREMULTIPLY_ALPHA_WEBGL` or
312
+ // `UNPACK_FLIP_Y_WEBGL` and not restore them. Set them
313
+ // explicitly so the upload result is deterministic.
314
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
315
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
316
+ gl.texImage2D(
317
+ gl.TEXTURE_2D,
318
+ 0,
319
+ gl.RGBA,
320
+ gl.RGBA,
321
+ gl.UNSIGNED_BYTE,
322
+ bmp,
323
+ );
324
+
325
+ // Do *not* `bmp.close()` here. The WebGL spec says
326
+ // `texImage2D(ImageBitmap)` consumes the source at call
327
+ // time, but several browser/driver combinations defer
328
+ // the actual GPU copy until the next draw; closing the
329
+ // bitmap before that copy lands leaves the texture
330
+ // valid-but-empty and the sampler returns (0,0,0,1) β€”
331
+ // i.e. solid black β€” for every tile after the first one
332
+ // or two whose upload happened to drain in time. The
333
+ // ImageBitmap is small (≀256Γ—256Γ—4 β‰ˆ 256 KB) and will
334
+ // be garbage-collected once the .then closure is
335
+ // released.
336
+ this._cache.set(gl, key, tex);
337
+ this._onTileLoad();
338
+ });
339
+ }
340
+
341
+ private _ensureProgram(glManager: WebGLContextManager): void {
342
+ if (this._program && this._cornerBuffer) {
343
+ return;
344
+ }
345
+
346
+ const gl = glManager.gl;
347
+ const program = glManager.shaders.getOrCreate(
348
+ "map-tile",
349
+ tileVert,
350
+ tileFrag,
351
+ );
352
+
353
+ this._program = {
354
+ program,
355
+ u_projection: gl.getUniformLocation(program, "u_projection"),
356
+ u_extent_min: gl.getUniformLocation(program, "u_extent_min"),
357
+ u_extent_max: gl.getUniformLocation(program, "u_extent_max"),
358
+ u_uv_min: gl.getUniformLocation(program, "u_uv_min"),
359
+ u_uv_max: gl.getUniformLocation(program, "u_uv_max"),
360
+ u_tile: gl.getUniformLocation(program, "u_tile"),
361
+ u_alpha: gl.getUniformLocation(program, "u_alpha"),
362
+ a_corner: gl.getAttribLocation(program, "a_corner"),
363
+ };
364
+
365
+ const buf = gl.createBuffer();
366
+ if (!buf) {
367
+ return;
368
+ }
369
+
370
+ gl.bindBuffer(gl.ARRAY_BUFFER, buf);
371
+ // Unit-quad corners in TRIANGLE_STRIP order:
372
+ // (0,0) (1,0) (0,1) (1,1)
373
+ // Stretched into Mercator space by `u_extent_*` uniforms in
374
+ // the vertex shader; UV picked by `u_uv_*`.
375
+ gl.bufferData(
376
+ gl.ARRAY_BUFFER,
377
+ new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]),
378
+ gl.STATIC_DRAW,
379
+ );
380
+ this._cornerBuffer = buf;
381
+ }
382
+ }
@@ -0,0 +1,143 @@
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
+ import type { TileSource } from "./tile-source";
14
+
15
+ /**
16
+ * One in-flight tile fetch. The same key (z/x/y under one source) only
17
+ * launches one fetch; concurrent requesters share the promise.
18
+ */
19
+ interface InFlight {
20
+ promise: Promise<ImageBitmap | null>;
21
+ abort: AbortController;
22
+ }
23
+
24
+ /**
25
+ * Async tile fetcher with in-flight dedup and abort-on-zoom-change.
26
+ *
27
+ * Runs inside the renderer worker (the chart's host process). `fetch`
28
+ * and `createImageBitmap` are both available in workers, and the
29
+ * resulting `ImageBitmap` can be uploaded straight to a WebGL2 texture
30
+ * via `gl.texImage2D(..., ImageBitmap)` β€” no main-thread bounce.
31
+ *
32
+ * The loader does not own GPU resources. Upload to texture happens in
33
+ * the tile layer once an `ImageBitmap` resolves; the layer then drops
34
+ * the bitmap (calling `close()` to free the decoded pixels).
35
+ */
36
+ export class TileLoader {
37
+ private _inFlight = new Map<string, InFlight>();
38
+ private _onLoad: () => void = () => {};
39
+
40
+ /**
41
+ * Register the "tile arrived" callback. The layer wires this to
42
+ * `chart.requestRender(glManager)` so a newly-loaded tile triggers
43
+ * exactly one extra frame.
44
+ */
45
+ setOnLoad(cb: () => void): void {
46
+ this._onLoad = cb;
47
+ }
48
+
49
+ /**
50
+ * Kick off a fetch (or return the in-flight one). The same
51
+ * (source.id, z, x, y) tuple only ever has one outstanding
52
+ * fetch; multiple callers share the result. Rejected fetches
53
+ * (network error, abort) resolve to `null` so callers can skip
54
+ * the tile without try/catch noise on every miss.
55
+ */
56
+ load(
57
+ source: TileSource,
58
+ z: number,
59
+ x: number,
60
+ y: number,
61
+ ): Promise<ImageBitmap | null> {
62
+ const key = tileKey(source.id, z, x, y);
63
+ const existing = this._inFlight.get(key);
64
+ if (existing) {
65
+ return existing.promise;
66
+ }
67
+
68
+ const abort = new AbortController();
69
+ const url = source.urlFor(z, x, y);
70
+ const promise = this._fetchAndDecode(url, abort.signal)
71
+ .then((bmp) => {
72
+ this._inFlight.delete(key);
73
+ if (bmp) {
74
+ this._onLoad();
75
+ }
76
+
77
+ return bmp;
78
+ })
79
+ .catch(() => {
80
+ this._inFlight.delete(key);
81
+ return null;
82
+ });
83
+
84
+ this._inFlight.set(key, { promise, abort });
85
+ return promise;
86
+ }
87
+
88
+ /**
89
+ * Abort every in-flight fetch. Called on view teardown / chart
90
+ * destroy. Fetches whose `Response` has already arrived but
91
+ * haven't yet decoded will still complete the decode and resolve
92
+ * to `null` (because `_onLoad` is replaced or the cache is gone)
93
+ * β€” harmless, no resource leak.
94
+ */
95
+ cancelAll(): void {
96
+ for (const entry of this._inFlight.values()) {
97
+ entry.abort.abort();
98
+ }
99
+
100
+ this._inFlight.clear();
101
+ }
102
+
103
+ /**
104
+ * Abort just the fetches whose key isn't in the supplied set.
105
+ * The layer calls this on every render with the currently-visible
106
+ * tile set so old-zoom requests don't keep arriving and triggering
107
+ * spurious re-renders after the user has moved on.
108
+ */
109
+ cancelExcept(liveKeys: Set<string>): void {
110
+ for (const [key, entry] of this._inFlight) {
111
+ if (!liveKeys.has(key)) {
112
+ entry.abort.abort();
113
+ this._inFlight.delete(key);
114
+ }
115
+ }
116
+ }
117
+
118
+ private async _fetchAndDecode(
119
+ url: string,
120
+ signal: AbortSignal,
121
+ ): Promise<ImageBitmap | null> {
122
+ const resp = await fetch(url, { signal });
123
+ if (!resp.ok) {
124
+ return null;
125
+ }
126
+
127
+ const blob = await resp.blob();
128
+ return await createImageBitmap(blob);
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Stable cache key for a tile under a given source. Embedded source id
134
+ * so swapping sources (light/dark) doesn't surface stale tiles.
135
+ */
136
+ export function tileKey(
137
+ sourceId: string,
138
+ z: number,
139
+ x: number,
140
+ y: number,
141
+ ): string {
142
+ return `${sourceId}/${z}/${x}/${y}`;
143
+ }
@@ -0,0 +1,156 @@
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
+ * A tile source describes *where* to fetch raster XYZ tiles and what
15
+ * attribution text the renderer must display in the chrome canvas.
16
+ * Implementations are stateless β€” the tile loader handles caching and
17
+ * in-flight dedup.
18
+ */
19
+ export interface TileSource {
20
+ /**
21
+ * Build the URL for tile (z, x, y). Implementations typically
22
+ * substitute a template like `{z}/{x}/{y}` and may rotate
23
+ * subdomains for browsers that throttle concurrent connections
24
+ * per host.
25
+ */
26
+ urlFor(z: number, x: number, y: number): string;
27
+
28
+ /**
29
+ * Plain-text attribution shown in the bottom-right of the chrome
30
+ * canvas. Required by every major tile provider's terms of use β€”
31
+ * do not suppress it without provider-side opt-out.
32
+ */
33
+ readonly attribution: string;
34
+
35
+ /**
36
+ * Side length of one tile in pixels. Tile providers ship 256 by
37
+ * default; a few offer 512 (`@2x`) variants. Used by the zoom-
38
+ * level picker to convert meters-per-pixel into a zoom level.
39
+ */
40
+ readonly tileSize: number;
41
+
42
+ /**
43
+ * Maximum zoom level the provider serves. Tiles requested above
44
+ * this fall back to the deepest available level with sub-tile
45
+ * UVs β€” same trick used during async loads.
46
+ */
47
+ readonly maxZoom: number;
48
+
49
+ /**
50
+ * Stable identifier for caching. Two `TileSource` instances with
51
+ * the same `id` share a tile cache; switching source (e.g. theme
52
+ * change) invalidates and re-fetches.
53
+ */
54
+ readonly id: string;
55
+ }
56
+
57
+ /**
58
+ * Subdomain-rotated URL template. Replaces `{s}` with one of the
59
+ * provided subdomains hashed by `(x + y)`, and `{z}`, `{x}`, `{y}`
60
+ * with the tile address. Most major tile providers fit this shape.
61
+ */
62
+ export class TemplatedTileSource implements TileSource {
63
+ constructor(
64
+ readonly id: string,
65
+ private readonly template: string,
66
+ readonly attribution: string,
67
+ readonly tileSize = 256,
68
+ readonly maxZoom = 19,
69
+ private readonly subdomains: readonly string[] = [],
70
+ ) {}
71
+
72
+ urlFor(z: number, x: number, y: number): string {
73
+ let url = this.template
74
+ .replace("{z}", String(z))
75
+ .replace("{x}", String(x))
76
+ .replace("{y}", String(y));
77
+ if (this.subdomains.length > 0) {
78
+ const idx = (x + y) % this.subdomains.length;
79
+ url = url.replace("{s}", this.subdomains[idx]);
80
+ }
81
+
82
+ return url;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Identifier of the default tile providers shipped with `viewer-charts`.
88
+ * Surfaced as the `map_tile_provider` PluginConfig enum so users can
89
+ * pick light vs. dark vs. labels-only without writing a custom source.
90
+ */
91
+ export type TileProviderId =
92
+ | "carto-positron"
93
+ | "carto-dark-matter"
94
+ | "carto-voyager";
95
+
96
+ /**
97
+ * CartoDB's "Positron" basemap β€” light, low-contrast, designed to sit
98
+ * behind a chart overlay. Default for light themes.
99
+ */
100
+ function cartoPositron(): TileSource {
101
+ return new TemplatedTileSource(
102
+ "carto-positron",
103
+ "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
104
+ "Β© OpenStreetMap contributors Β© CARTO",
105
+ 256,
106
+ 19,
107
+ ["a", "b", "c", "d"],
108
+ );
109
+ }
110
+
111
+ /**
112
+ * CartoDB's "Dark Matter" basemap β€” dark, low-contrast. Default for
113
+ * dark themes.
114
+ */
115
+ function cartoDarkMatter(): TileSource {
116
+ return new TemplatedTileSource(
117
+ "carto-dark-matter",
118
+ "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
119
+ "Β© OpenStreetMap contributors Β© CARTO",
120
+ 256,
121
+ 19,
122
+ ["a", "b", "c", "d"],
123
+ );
124
+ }
125
+
126
+ /**
127
+ * CartoDB's "Voyager" basemap β€” full color, more land/water contrast
128
+ * than Positron. Good when the chart glyphs are translucent.
129
+ */
130
+ function cartoVoyager(): TileSource {
131
+ return new TemplatedTileSource(
132
+ "carto-voyager",
133
+ "https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png",
134
+ "Β© OpenStreetMap contributors Β© CARTO",
135
+ 256,
136
+ 19,
137
+ ["a", "b", "c", "d"],
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Resolve a `TileProviderId` (from PluginConfig) to a concrete
143
+ * `TileSource`. Unknown ids fall back to Positron so a misconfigured
144
+ * `_pluginConfig` never produces a blank map.
145
+ */
146
+ export function tileSourceFor(id: TileProviderId | string): TileSource {
147
+ switch (id) {
148
+ case "carto-dark-matter":
149
+ return cartoDarkMatter();
150
+ case "carto-voyager":
151
+ return cartoVoyager();
152
+ case "carto-positron":
153
+ default:
154
+ return cartoPositron();
155
+ }
156
+ }