@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,19 @@
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
+ precision highp float;
14
+
15
+ varying vec4 v_color;
16
+
17
+ void main() {
18
+ gl_FragColor = v_color;
19
+ }
@@ -0,0 +1,79 @@
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
+ // Per-vertex (divisor=0): position in the unit triangle-strip template.
14
+ // a_strip_t ∈ [0, 1] angular parameter along the arc
15
+ // a_side ∈ {0, 1} 0 = inner radius, 1 = outer radius
16
+ attribute float a_strip_t;
17
+ attribute float a_side;
18
+
19
+ // Per-instance (divisor=1) arc geometry.
20
+ attribute vec2 a_angles; // (a0, a1) in radians
21
+ attribute vec2 a_radii; // (r0, r1) inner / outer pixel radius
22
+ attribute vec4 a_color;
23
+
24
+ uniform vec2 u_center; // chart center in pixel space
25
+ uniform vec2 u_resolution; // viewport size in device pixels
26
+ uniform float u_border_px; // symmetric inset, in device pixels
27
+
28
+ varying vec4 v_color;
29
+ varying vec2 v_edge; // (angular t, radial t) for optional AA fringe
30
+
31
+ void main() {
32
+ // Symmetric inset: shrink the arc by half the border on every edge
33
+ // so adjacent arcs each give up half, producing a
34
+ // `u_border_px`-wide gap in pixel space.
35
+ //
36
+ // Radial inset is constant; angular inset is computed per-vertex
37
+ // based on the vertex's *actual* radius. This keeps the angular gap
38
+ // at exactly `u_border_px` pixels wide at every radial position —
39
+ // without it, the gap narrows toward the center (since the same
40
+ // dTheta corresponds to fewer pixels at smaller r).
41
+ //
42
+ // Side effect: the arc's side edges are slightly non-radial (they
43
+ // curve so that the outer endpoint sits inside the inner endpoint's
44
+ // wedge). At 1 px borders this is imperceptible; at very wide
45
+ // borders the arc looks like a thin curved parallelogram — which
46
+ // is, in fact, the shape with constant pixel-width gaps.
47
+ float half_border = u_border_px * 0.5;
48
+
49
+ float adjR0 = a_radii.x + half_border;
50
+ float adjR1 = a_radii.y - half_border;
51
+ if(adjR1 <= adjR0) {
52
+ // Arc thinner than the border radially — nothing to draw.
53
+ gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
54
+ v_color = vec4(0.0);
55
+ v_edge = vec2(0.0);
56
+ return;
57
+ }
58
+
59
+ // Per-vertex radius + angular inset. dTheta scales with 1/r so the
60
+ // pixel-space gap is constant at every radial position. Clamp to
61
+ // at most `span / 2` so narrow arcs collapse their inner edge to
62
+ // a point at the midpoint angle instead of inverting (which would
63
+ // send inner vertices past outer ones and rasterize degenerate
64
+ // triangles across the screen). Every vertex of an instance takes
65
+ // the same clamp path, so the triangle strip stays well-formed.
66
+ float r = mix(adjR0, adjR1, a_side);
67
+ float dTheta = half_border / max(r, 1.0);
68
+ float span = a_angles.y - a_angles.x;
69
+ dTheta = min(dTheta, span * 0.5);
70
+ float angle = mix(a_angles.x + dTheta, a_angles.y - dTheta, a_strip_t);
71
+
72
+ vec2 pixel = u_center + vec2(cos(angle), sin(angle)) * r;
73
+ // Pixel → clip: origin top-left, Y flipped to match 2D canvas conv.
74
+ vec2 clip = (pixel / u_resolution) * 2.0 - 1.0;
75
+ gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
76
+
77
+ v_color = a_color;
78
+ v_edge = vec2(a_strip_t, a_side);
79
+ }
@@ -0,0 +1,27 @@
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
+ precision highp float;
14
+
15
+ varying vec2 v_uv;
16
+
17
+ uniform sampler2D u_tile;
18
+
19
+ // Optional alpha multiplier (0.0..1.0). Used to fade the tile basemap
20
+ // when the chart's theme prefers a subdued backdrop behind the glyph
21
+ // layer; defaults to 1.0 so callers can omit it.
22
+ uniform float u_alpha;
23
+
24
+ void main() {
25
+ vec4 c = texture2D(u_tile, v_uv);
26
+ gl_FragColor = vec4(c.rgb, c.a * u_alpha);
27
+ }
@@ -0,0 +1,35 @@
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
+ // Unit-quad vertex shader for one map tile. The same static [0..1]
14
+ // corner buffer is bound for every tile in a frame; per-tile uniforms
15
+ // stretch the corner into Mercator space and pick the UV sub-rect.
16
+ // Sub-rect support is for the parent-tile fallback path (sample the
17
+ // loaded parent at the child's quadrant while the child fetches).
18
+ attribute vec2 a_corner;
19
+
20
+ uniform mat4 u_projection;
21
+ uniform vec2 u_extent_min;
22
+ uniform vec2 u_extent_max;
23
+ uniform vec2 u_uv_min;
24
+ uniform vec2 u_uv_max;
25
+
26
+ varying vec2 v_uv;
27
+
28
+ void main() {
29
+ vec2 pos = mix(u_extent_min, u_extent_max, a_corner);
30
+ // Tiles ship north-up; the WebGL Y axis points up at clip
31
+ // coordinates so flip the V channel before sampling.
32
+ vec2 uv = mix(u_uv_min, u_uv_max, vec2(a_corner.x, 1.0 - a_corner.y));
33
+ v_uv = uv;
34
+ gl_Position = u_projection * vec4(pos, 0.0, 1.0);
35
+ }
@@ -0,0 +1,19 @@
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
+ precision highp float;
14
+
15
+ varying vec4 v_color;
16
+
17
+ void main() {
18
+ gl_FragColor = v_color;
19
+ }
@@ -0,0 +1,25 @@
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
+ attribute vec2 a_position;
14
+ attribute vec4 a_color;
15
+
16
+ uniform vec2 u_resolution;
17
+
18
+ varying vec4 v_color;
19
+
20
+ void main() {
21
+ vec2 clip = (a_position / u_resolution) * 2.0 - 1.0;
22
+ clip.y = -clip.y;
23
+ gl_Position = vec4(clip, 0.0, 1.0);
24
+ v_color = a_color;
25
+ }
@@ -0,0 +1,30 @@
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
+ precision highp float;
14
+
15
+ varying vec3 v_color;
16
+ varying float v_point_size;
17
+
18
+ void main() {
19
+ vec2 coord = gl_PointCoord - vec2(0.5);
20
+ float dist = length(coord);
21
+
22
+ if(dist > 0.5)
23
+ discard;
24
+
25
+ // Anti-alias at the circle edge.
26
+ float pixelWidth = 1.5 / max(v_point_size, 1.0);
27
+ float alpha = 1.0 - smoothstep(0.5 - pixelWidth, 0.5, dist);
28
+
29
+ gl_FragColor = vec4(v_color, alpha);
30
+ }
@@ -0,0 +1,31 @@
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
+ // Per-vertex: one point per (series, category) sample. Color is carried
14
+ // per-vertex so a single draw call can render every scatter series with
15
+ // its palette color, no gradient lookup.
16
+
17
+ attribute vec2 a_position;
18
+ attribute vec3 a_color;
19
+
20
+ uniform mat4 u_projection;
21
+ uniform float u_point_size;
22
+
23
+ varying vec3 v_color;
24
+ varying float v_point_size;
25
+
26
+ void main() {
27
+ gl_Position = u_projection * vec4(a_position, 0.0, 1.0);
28
+ gl_PointSize = u_point_size;
29
+ v_point_size = u_point_size;
30
+ v_color = a_color;
31
+ }
@@ -0,0 +1,312 @@
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 { parseCSSColorToVec3 } from "../utils/css";
14
+
15
+ /**
16
+ * A single stop on a parsed CSS gradient. `offset` ∈ [0, 1].
17
+ */
18
+ export interface GradientStop {
19
+ offset: number;
20
+ color: [number, number, number, number]; // RGBA, each ∈ [0, 1]
21
+ }
22
+
23
+ const DEFAULT_STOPS: GradientStop[] = [
24
+ { offset: 0, color: [0x03 / 255, 0x66 / 255, 0xd6 / 255, 1] },
25
+ { offset: 1, color: [0xff / 255, 0x7f / 255, 0x0e / 255, 1] },
26
+ ];
27
+
28
+ /**
29
+ * Parse a `linear-gradient(...)` CSS expression into ordered stops. Tolerates
30
+ * missing percentages (distributes linearly between known offsets, matching
31
+ * the CSS standard) and leading direction tokens (`to right`, `90deg`, etc.)
32
+ * which are simply skipped.
33
+ *
34
+ * Returns the default blue → orange two-stop on any parse failure so themes
35
+ * that never set the gradient still produce sane output.
36
+ */
37
+ export function parseCssGradient(
38
+ src: string | null | undefined,
39
+ ): GradientStop[] {
40
+ if (!src) {
41
+ return DEFAULT_STOPS.slice();
42
+ }
43
+
44
+ const trimmed = src.trim();
45
+ if (!trimmed) {
46
+ return DEFAULT_STOPS.slice();
47
+ }
48
+
49
+ // Strip the `linear-gradient(` wrapper. Bail out if we don't find it.
50
+ const openIdx = trimmed.indexOf("(");
51
+ if (openIdx < 0) {
52
+ return DEFAULT_STOPS.slice();
53
+ }
54
+
55
+ if (!/^linear-gradient\s*\(/i.test(trimmed)) {
56
+ return DEFAULT_STOPS.slice();
57
+ }
58
+
59
+ const closeIdx = trimmed.lastIndexOf(")");
60
+ if (closeIdx <= openIdx) {
61
+ return DEFAULT_STOPS.slice();
62
+ }
63
+
64
+ const body = trimmed.substring(openIdx + 1, closeIdx);
65
+
66
+ // Split on commas at depth 0 (respecting nested `rgb(...)` / `rgba(...)` /
67
+ // `hsl(...)` parens which also contain commas).
68
+ const parts: string[] = [];
69
+ let depth = 0;
70
+ let start = 0;
71
+ for (let i = 0; i < body.length; i++) {
72
+ const ch = body[i];
73
+ if (ch === "(") {
74
+ depth++;
75
+ } else if (ch === ")") {
76
+ depth--;
77
+ } else if (ch === "," && depth === 0) {
78
+ parts.push(body.substring(start, i));
79
+ start = i + 1;
80
+ }
81
+ }
82
+
83
+ parts.push(body.substring(start));
84
+
85
+ // First part may be a direction (`to right`, `90deg`, `to bottom right`)
86
+ // rather than a color-stop. Detect by: no matching color syntax.
87
+ const stops: Array<{
88
+ color: [number, number, number];
89
+ offset: number | null;
90
+ }> = [];
91
+ let startIdx = 0;
92
+ if (parts.length > 0) {
93
+ const firstNorm = parts[0].trim().toLowerCase();
94
+ if (
95
+ firstNorm.startsWith("to ") ||
96
+ /^[-\d.]+(deg|rad|grad|turn)/.test(firstNorm)
97
+ ) {
98
+ startIdx = 1;
99
+ }
100
+ }
101
+
102
+ for (let i = startIdx; i < parts.length; i++) {
103
+ const piece = parts[i].trim();
104
+ if (!piece) {
105
+ continue;
106
+ }
107
+
108
+ // Peel off an optional trailing `<number>%` or `<number>px`.
109
+ const pctMatch = piece.match(/\s([\-\d.]+)%\s*$/);
110
+ const color = pctMatch
111
+ ? piece.substring(0, pctMatch.index).trim()
112
+ : piece;
113
+ const offset = pctMatch ? parseFloat(pctMatch[1]) / 100 : null;
114
+ try {
115
+ const rgb = parseCSSColorToVec3(color);
116
+ stops.push({ color: rgb, offset });
117
+ } catch {
118
+ // skip unparseable stop
119
+ }
120
+ }
121
+
122
+ if (stops.length === 0) {
123
+ return DEFAULT_STOPS.slice();
124
+ }
125
+
126
+ if (stops.length === 1) {
127
+ // Single stop → solid color. Duplicate across [0, 1] so sampling works.
128
+ const [r, g, b] = stops[0].color;
129
+ return [
130
+ { offset: 0, color: [r, g, b, 1] },
131
+ { offset: 1, color: [r, g, b, 1] },
132
+ ];
133
+ }
134
+
135
+ // Fill in missing offsets by linear interpolation of neighbours with
136
+ // known positions (CSS implicit-position semantics).
137
+ if (stops[0].offset === null) {
138
+ stops[0].offset = 0;
139
+ }
140
+
141
+ if (stops[stops.length - 1].offset === null) {
142
+ stops[stops.length - 1].offset = 1;
143
+ }
144
+
145
+ for (let i = 1; i < stops.length - 1; i++) {
146
+ if (stops[i].offset !== null) {
147
+ continue;
148
+ }
149
+
150
+ // Find next known offset.
151
+ let j = i + 1;
152
+ while (j < stops.length && stops[j].offset === null) {
153
+ j++;
154
+ }
155
+
156
+ const before = stops[i - 1].offset!;
157
+ const after = stops[j].offset!;
158
+ const span = j - (i - 1);
159
+ for (let k = i; k < j; k++) {
160
+ stops[k].offset =
161
+ before + ((k - (i - 1)) / span) * (after - before);
162
+ }
163
+
164
+ i = j - 1;
165
+ }
166
+
167
+ // Clamp offsets to [0, 1] and ensure non-decreasing order.
168
+ let prev = 0;
169
+ const result: GradientStop[] = stops.map((s) => {
170
+ const off = Math.max(prev, Math.min(1, s.offset!));
171
+ prev = off;
172
+ return {
173
+ offset: off,
174
+ color: [s.color[0], s.color[1], s.color[2], 1],
175
+ };
176
+ });
177
+
178
+ return result;
179
+ }
180
+
181
+ /**
182
+ * Piecewise-linear color sample at `t ∈ [0, 1]`. Returns RGBA in [0, 1].
183
+ * Clamps `t` to the gradient's first/last stop outside `[0, 1]`.
184
+ */
185
+ export function sampleGradient(
186
+ stops: GradientStop[],
187
+ t: number,
188
+ ): [number, number, number, number] {
189
+ if (stops.length === 0) {
190
+ return [0, 0, 0, 1];
191
+ }
192
+
193
+ if (t <= stops[0].offset) {
194
+ return stops[0].color.slice() as [number, number, number, number];
195
+ }
196
+
197
+ const last = stops[stops.length - 1];
198
+ if (t >= last.offset) {
199
+ return last.color.slice() as [number, number, number, number];
200
+ }
201
+
202
+ // Bisect for the interval containing `t`.
203
+ let lo = 0;
204
+ let hi = stops.length - 1;
205
+ while (hi - lo > 1) {
206
+ const mid = (lo + hi) >> 1;
207
+ if (stops[mid].offset <= t) {
208
+ lo = mid;
209
+ } else {
210
+ hi = mid;
211
+ }
212
+ }
213
+
214
+ const a = stops[lo];
215
+ const b = stops[hi];
216
+ const span = b.offset - a.offset;
217
+ const u = span > 0 ? (t - a.offset) / span : 0;
218
+ return [
219
+ a.color[0] + (b.color[0] - a.color[0]) * u,
220
+ a.color[1] + (b.color[1] - a.color[1]) * u,
221
+ a.color[2] + (b.color[2] - a.color[2]) * u,
222
+ a.color[3] + (b.color[3] - a.color[3]) * u,
223
+ ];
224
+ }
225
+
226
+ /**
227
+ * Sign-aware normalization. Returns `t ∈ [0, 1]` where the 50% stop is
228
+ * always the sign pivot:
229
+ * - crosses zero → `[-maxAbs, maxAbs]` stretched symmetrically; 0 → 0.5.
230
+ * - all-positive → `[0, colorMax]` occupies top half `[0.5, 1]`.
231
+ * - all-negative → `[colorMin, 0]` occupies bottom half `[0, 0.5]`.
232
+ * - degenerate → 0.5 (single colour at the midpoint).
233
+ */
234
+ export function colorValueToT(
235
+ value: number,
236
+ colorMin: number,
237
+ colorMax: number,
238
+ ): number {
239
+ if (!isFinite(value) || colorMin === colorMax) {
240
+ return 0.5;
241
+ }
242
+
243
+ let denom: number;
244
+ if (colorMin >= 0) {
245
+ denom = colorMax;
246
+ } else if (colorMax <= 0) {
247
+ denom = -colorMin;
248
+ } else {
249
+ denom = Math.max(-colorMin, colorMax);
250
+ }
251
+
252
+ if (denom <= 0) {
253
+ return 0.5;
254
+ }
255
+
256
+ const t = 0.5 + 0.5 * (value / denom);
257
+ return t < 0 ? 0 : t > 1 ? 1 : t;
258
+ }
259
+
260
+ /**
261
+ * Convert a discrete series palette (from `--psp-charts--series-N--color`)
262
+ * into a `GradientStop[]` with stops at `i / (N - 1)`. The resulting
263
+ * stops can feed `buildGradientLUT` / `ensureGradientTexture` / any
264
+ * other code path that already accepts a gradient — so categorical
265
+ * coloring and numeric gradients share one LUT pipeline. Integer idx
266
+ * sampling via `t = idx / (N - 1)` lands exactly on a palette color;
267
+ * the linear blend between stops is only hit by non-integer samples
268
+ * (which categorical data doesn't produce).
269
+ */
270
+ export function paletteToStops(
271
+ palette: [number, number, number][],
272
+ ): GradientStop[] {
273
+ if (palette.length === 0) {
274
+ return DEFAULT_STOPS.slice();
275
+ }
276
+
277
+ if (palette.length === 1) {
278
+ const [r, g, b] = palette[0];
279
+ return [
280
+ { offset: 0, color: [r, g, b, 1] },
281
+ { offset: 1, color: [r, g, b, 1] },
282
+ ];
283
+ }
284
+
285
+ const denom = palette.length - 1;
286
+ return palette.map(([r, g, b], i) => ({
287
+ offset: i / denom,
288
+ color: [r, g, b, 1],
289
+ }));
290
+ }
291
+
292
+ /**
293
+ * Bake a sampled LUT for GPU upload as RGBA8 (`size × 1`). Default 256
294
+ * samples — visually indistinguishable from a denser sample at typical
295
+ * viewport sizes and keeps the texture tiny (1 KB).
296
+ */
297
+ export function buildGradientLUT(
298
+ stops: GradientStop[],
299
+ size: number = 256,
300
+ ): Uint8Array {
301
+ const out = new Uint8Array(size * 4);
302
+ for (let i = 0; i < size; i++) {
303
+ const t = size === 1 ? 0 : i / (size - 1);
304
+ const c = sampleGradient(stops, t);
305
+ out[i * 4] = Math.round(c[0] * 255);
306
+ out[i * 4 + 1] = Math.round(c[1] * 255);
307
+ out[i * 4 + 2] = Math.round(c[2] * 255);
308
+ out[i * 4 + 3] = Math.round(c[3] * 255);
309
+ }
310
+
311
+ return out;
312
+ }
@@ -0,0 +1,64 @@
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 { sampleGradient, type GradientStop } from "./gradient";
14
+
15
+ export type Vec3 = [number, number, number];
16
+
17
+ /**
18
+ * Build a series palette of length `count` by sampling the theme gradient
19
+ * at evenly-spaced offsets. For count == 1 returns the 50% stop.
20
+ */
21
+ export function interpolatePalette(
22
+ stops: GradientStop[],
23
+ count: number,
24
+ ): Vec3[] {
25
+ if (count <= 0) {
26
+ return [];
27
+ }
28
+
29
+ const out: Vec3[] = new Array(count);
30
+ for (let i = 0; i < count; i++) {
31
+ const t = count === 1 ? 0.5 : i / (count - 1);
32
+ const c = sampleGradient(stops, t);
33
+ out[i] = [c[0], c[1], c[2]];
34
+ }
35
+
36
+ return out;
37
+ }
38
+
39
+ /**
40
+ * Resolve a series palette: use the discrete `--psp-charts--series-N--color`
41
+ * palette when available, otherwise fall back to evenly-spaced samples of
42
+ * the theme gradient.
43
+ */
44
+ export function resolvePalette(
45
+ discrete: Vec3[],
46
+ stops: GradientStop[],
47
+ count: number,
48
+ ): Vec3[] {
49
+ if (discrete.length > 0) {
50
+ if (discrete.length >= count) {
51
+ return discrete.slice(0, count);
52
+ }
53
+
54
+ // Cycle through the discrete palette for overflow indices.
55
+ const out: Vec3[] = new Array(count);
56
+ for (let i = 0; i < count; i++) {
57
+ out[i] = discrete[i % discrete.length];
58
+ }
59
+
60
+ return out;
61
+ }
62
+
63
+ return interpolatePalette(stops, count);
64
+ }