@chartts/gl 0.1.3 → 0.1.4

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.
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ <p align="center">
2
+ <a href="https://www.npmjs.com/package/@chartts/gl"><img src="https://img.shields.io/npm/v/@chartts/gl?color=06B6D4&label=npm" alt="npm version" /></a>
3
+ <a href="https://github.com/chartts/chartts/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-06B6D4" alt="MIT License" /></a>
4
+ <a href="https://chartts.com"><img src="https://img.shields.io/badge/docs-chartts.com-06B6D4" alt="Documentation" /></a>
5
+ </p>
6
+
7
+ # @chartts/gl
8
+
9
+ WebGL chart types for Chartts. GPU-accelerated 3D and high-performance 2D charts.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install @chartts/gl @chartts/core
15
+ ```
16
+
17
+ ## Chart types
18
+
19
+ | Type | Import |
20
+ |------|--------|
21
+ | 3D Bar | `@chartts/gl/bar3d` |
22
+ | 3D Line | `@chartts/gl/line3d` |
23
+ | 3D Scatter | `@chartts/gl/scatter3d` |
24
+ | 3D Surface | `@chartts/gl/surface3d` |
25
+ | 3D Globe | `@chartts/gl/globe3d` |
26
+ | GL Scatter (2D, 100k+ points) | `@chartts/gl/scatter-gl` |
27
+ | GL Lines (2D, high-perf) | `@chartts/gl/lines-gl` |
28
+ | GL Flow (particle animation) | `@chartts/gl/flow-gl` |
29
+ | GL Graph (force layout) | `@chartts/gl/graph-gl` |
30
+ | 3D Lines | `@chartts/gl/lines3d` |
31
+ | 3D Map | `@chartts/gl/map3d` |
32
+
33
+ ## Usage
34
+
35
+ ```ts
36
+ import { createGLChart } from "@chartts/gl"
37
+ import { bar3dChartType } from "@chartts/gl/bar3d"
38
+
39
+ const chart = createGLChart(container, bar3dChartType, data, {
40
+ width: 800,
41
+ height: 600,
42
+ })
43
+ ```
44
+
45
+ ## Part of Chartts
46
+
47
+ Beautiful charts. Tiny bundle. Every framework.
48
+
49
+ - [Documentation](https://chartts.com/docs)
50
+ - [GitHub](https://github.com/chartts/chartts)
51
+ - [All packages](https://www.npmjs.com/org/chartts)
52
+
53
+ ## License
54
+
55
+ MIT
package/dist/bar3d.cjs CHANGED
@@ -1,20 +1,20 @@
1
1
  'use strict';
2
2
 
3
- var chunkQ4JAQOV3_cjs = require('./chunk-Q4JAQOV3.cjs');
3
+ var chunkJCECG2KY_cjs = require('./chunk-JCECG2KY.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "Bar3D", {
8
8
  enumerable: true,
9
- get: function () { return chunkQ4JAQOV3_cjs.Bar3D; }
9
+ get: function () { return chunkJCECG2KY_cjs.Bar3D; }
10
10
  });
11
11
  Object.defineProperty(exports, "createBar3DPlugin", {
12
12
  enumerable: true,
13
- get: function () { return chunkQ4JAQOV3_cjs.createBar3DPlugin; }
13
+ get: function () { return chunkJCECG2KY_cjs.createBar3DPlugin; }
14
14
  });
15
15
  Object.defineProperty(exports, "createGLChart", {
16
16
  enumerable: true,
17
- get: function () { return chunkQ4JAQOV3_cjs.createGLChart; }
17
+ get: function () { return chunkJCECG2KY_cjs.createGLChart; }
18
18
  });
19
19
  //# sourceMappingURL=bar3d.cjs.map
20
20
  //# sourceMappingURL=bar3d.cjs.map
package/dist/bar3d.js CHANGED
@@ -1,3 +1,3 @@
1
- export { Bar3D, createBar3DPlugin, createGLChart } from './chunk-M24XMYGG.js';
1
+ export { Bar3D, createBar3DPlugin, createGLChart } from './chunk-XGQDO4VO.js';
2
2
  //# sourceMappingURL=bar3d.js.map
3
3
  //# sourceMappingURL=bar3d.js.map
@@ -1813,20 +1813,36 @@ function createSurface3DPlugin() {
1813
1813
  }
1814
1814
 
1815
1815
  // src/charts/globe3d/globe3d-type.ts
1816
+ var GLOBE_RADIUS = 3;
1817
+ var SEGMENTS = 128;
1818
+ var RINGS = 64;
1816
1819
  function latLngToXYZ(lat, lng, radius) {
1817
1820
  const phi = (90 - lat) * Math.PI / 180;
1818
1821
  const theta = (lng + 180) * Math.PI / 180;
1819
- return [-radius * Math.sin(phi) * Math.cos(theta), radius * Math.cos(phi), radius * Math.sin(phi) * Math.sin(theta)];
1822
+ return [
1823
+ -radius * Math.sin(phi) * Math.cos(theta),
1824
+ radius * Math.cos(phi),
1825
+ radius * Math.sin(phi) * Math.sin(theta)
1826
+ ];
1827
+ }
1828
+ function angularDistance(lat1, lng1, lat2, lng2) {
1829
+ const p1 = lat1 * Math.PI / 180, p2 = lat2 * Math.PI / 180;
1830
+ const dl = (lng2 - lng1) * Math.PI / 180;
1831
+ return Math.acos(
1832
+ Math.min(1, Math.max(
1833
+ -1,
1834
+ Math.sin(p1) * Math.sin(p2) + Math.cos(p1) * Math.cos(p2) * Math.cos(dl)
1835
+ ))
1836
+ );
1820
1837
  }
1821
1838
  function createGlobe3DPlugin() {
1822
1839
  let sphereVBO = null;
1823
1840
  let sphereIBO = null;
1824
1841
  let sphereIndexCount = 0;
1825
- let pointVBO = null;
1826
- let pointCount = 0;
1827
1842
  const modelMatrix = mat4();
1828
1843
  const normalMatrix = new Float32Array(9);
1829
- let globePoints = [];
1844
+ let screenPoints = [];
1845
+ let seriesInfo = [];
1830
1846
  return {
1831
1847
  type: "globe3d",
1832
1848
  prepare(ctx) {
@@ -1840,93 +1856,178 @@ function createGlobe3DPlugin() {
1840
1856
  [...MESH_VERT_UNIFORMS, ...MESH_FRAG_UNIFORMS],
1841
1857
  MESH_VERT_ATTRIBUTES
1842
1858
  );
1843
- renderer.registerProgram(
1844
- "point3d",
1845
- POINT_VERT,
1846
- POINT_FRAG,
1847
- [...POINT_VERT_UNIFORMS, ...POINT_FRAG_UNIFORMS],
1848
- POINT_VERT_ATTRIBUTES
1849
- );
1850
- const globeRadius = 3;
1851
- const segments = 64, rings = 32;
1852
- const sphereColor = [0.12, 0.22, 0.48];
1853
- const sv = [], si = [];
1854
- for (let ring = 0; ring <= rings; ring++) {
1855
- const phi = ring / rings * Math.PI;
1856
- for (let seg = 0; seg <= segments; seg++) {
1857
- const theta = seg / segments * Math.PI * 2;
1858
- const nx = Math.sin(phi) * Math.cos(theta), ny = Math.cos(phi), nz = Math.sin(phi) * Math.sin(theta);
1859
- sv.push(nx * globeRadius, ny * globeRadius, nz * globeRadius, nx, ny, nz, ...sphereColor);
1860
- }
1861
- }
1862
- for (let ring = 0; ring < rings; ring++) for (let seg = 0; seg < segments; seg++) {
1863
- const a = ring * (segments + 1) + seg, b = a + segments + 1;
1864
- si.push(a, b, a + 1, a + 1, b, b + 1);
1865
- }
1866
- if (sphereVBO) sphereVBO.update(new Float32Array(sv));
1867
- else sphereVBO = createVertexBuffer(gl, new Float32Array(sv), gl.STATIC_DRAW);
1868
- if (sphereIBO) sphereIBO.update(new Uint16Array(si));
1869
- else sphereIBO = createIndexBuffer(gl, new Uint16Array(si), gl.STATIC_DRAW);
1870
- sphereIndexCount = si.length;
1871
- globePoints = [];
1872
- const pv = [];
1859
+ const patches = [];
1860
+ let maxVal = 0;
1861
+ for (const s of series) for (const v of s.values) if (Math.abs(v) > maxVal) maxVal = Math.abs(v);
1862
+ if (maxVal === 0) maxVal = 1;
1863
+ seriesInfo = [];
1864
+ screenPoints = [];
1873
1865
  for (let sIdx = 0; sIdx < series.length; sIdx++) {
1874
1866
  const s = series[sIdx];
1875
- const color = hexToRGB(s.color ?? theme.colors[sIdx % theme.colors.length]);
1867
+ const colorHex = s.color ?? theme.colors[sIdx % theme.colors.length];
1868
+ const color = hexToRGB(colorHex);
1869
+ seriesInfo.push({ name: s.name, color: colorHex });
1876
1870
  for (let di = 0; di < s.values.length; di++) {
1877
1871
  const lat = s.y?.[di] ?? 0, lng = s.x?.[di] ?? 0, value = s.values[di];
1878
- const [wx, wy, wz] = latLngToXYZ(lat, lng, globeRadius * (1 + value * 5e-3));
1879
- pv.push(wx, wy, wz, color[0], color[1], color[2], Math.max(4, value * 0.3));
1880
- globePoints.push({ si: sIdx, di, lat, lng, wx, wy, wz, value, name: s.name });
1872
+ const normValue = Math.abs(value) / maxVal;
1873
+ patches.push({ lat, lng, value, normValue, color, si: sIdx, di, name: s.name, label: data.categories?.[di] ?? "" });
1874
+ const [wx, wy, wz] = latLngToXYZ(lat, lng, GLOBE_RADIUS);
1875
+ screenPoints.push({ si: sIdx, di, lat, lng, wx, wy, wz, value, name: s.name, label: data.categories?.[di] ?? "" });
1881
1876
  }
1882
1877
  }
1883
- if (pointVBO) pointVBO.update(new Float32Array(pv));
1884
- else pointVBO = createVertexBuffer(gl, new Float32Array(pv), gl.DYNAMIC_DRAW);
1885
- pointCount = globePoints.length;
1878
+ const baseColor = [0.03, 0.05, 0.12];
1879
+ const patchRadius = 0.8;
1880
+ const gratLatStep = 30, gratLngStep = 30;
1881
+ const gratWidth = 0.015;
1882
+ const gratColor = [0.08, 0.12, 0.22];
1883
+ console.log(`[Globe3D] Building sphere ${SEGMENTS}x${RINGS}, ${patches.length} data patches`);
1884
+ const verts = [];
1885
+ const indices = [];
1886
+ let patchedVerts = 0;
1887
+ for (let ring = 0; ring <= RINGS; ring++) {
1888
+ const phi = ring / RINGS * Math.PI;
1889
+ const lat = 90 - ring / RINGS * 180;
1890
+ for (let seg = 0; seg <= SEGMENTS; seg++) {
1891
+ const theta = seg / SEGMENTS * Math.PI * 2;
1892
+ const lng = seg / SEGMENTS * 360 - 180;
1893
+ const nx = Math.sin(phi) * Math.cos(theta);
1894
+ const ny = Math.cos(phi);
1895
+ const nz = Math.sin(phi) * Math.sin(theta);
1896
+ let r = baseColor[0], g = baseColor[1], b = baseColor[2];
1897
+ const latMod = (lat % gratLatStep + gratLatStep) % gratLatStep;
1898
+ const latDist = Math.min(latMod, gratLatStep - latMod) * Math.PI / 180;
1899
+ const lngMod = (lng % gratLngStep + gratLngStep) % gratLngStep;
1900
+ const lngDist = Math.min(lngMod, gratLngStep - lngMod) * Math.PI / 180;
1901
+ const gratDist = Math.min(latDist, lngDist);
1902
+ if (gratDist < gratWidth) {
1903
+ const gratT = 1 - gratDist / gratWidth;
1904
+ r = r + (gratColor[0] - r) * gratT;
1905
+ g = g + (gratColor[1] - g) * gratT;
1906
+ b = b + (gratColor[2] - b) * gratT;
1907
+ }
1908
+ let ar = 0, ag = 0, ab = 0;
1909
+ for (const patch of patches) {
1910
+ const dist = angularDistance(lat, lng, patch.lat, patch.lng);
1911
+ if (dist < patchRadius) {
1912
+ const t = 1 - dist / patchRadius;
1913
+ const influence = t * (0.4 + t * 0.6) * patch.normValue;
1914
+ ar += patch.color[0] * influence;
1915
+ ag += patch.color[1] * influence;
1916
+ ab += patch.color[2] * influence;
1917
+ }
1918
+ }
1919
+ if (ar > 0.01 || ag > 0.01 || ab > 0.01) {
1920
+ patchedVerts++;
1921
+ r = Math.min(1, r + ar);
1922
+ g = Math.min(1, g + ag);
1923
+ b = Math.min(1, b + ab);
1924
+ }
1925
+ verts.push(
1926
+ nx * GLOBE_RADIUS,
1927
+ ny * GLOBE_RADIUS,
1928
+ nz * GLOBE_RADIUS,
1929
+ nx,
1930
+ ny,
1931
+ nz,
1932
+ r,
1933
+ g,
1934
+ b
1935
+ );
1936
+ }
1937
+ }
1938
+ console.log(`[Globe3D] ${patchedVerts} / ${(RINGS + 1) * (SEGMENTS + 1)} vertices colored by data`);
1939
+ for (let ring = 0; ring < RINGS; ring++) {
1940
+ for (let seg = 0; seg < SEGMENTS; seg++) {
1941
+ const a = ring * (SEGMENTS + 1) + seg;
1942
+ const b = a + SEGMENTS + 1;
1943
+ indices.push(a, b, a + 1, a + 1, b, b + 1);
1944
+ }
1945
+ }
1946
+ const vertArr = new Float32Array(verts);
1947
+ const idxArr = new Uint16Array(indices);
1948
+ if (sphereVBO) sphereVBO.update(vertArr);
1949
+ else sphereVBO = createVertexBuffer(gl, vertArr, gl.DYNAMIC_DRAW);
1950
+ if (sphereIBO) sphereIBO.update(idxArr);
1951
+ else sphereIBO = createIndexBuffer(gl, idxArr, gl.DYNAMIC_DRAW);
1952
+ sphereIndexCount = indices.length;
1886
1953
  mat4Identity(modelMatrix);
1887
1954
  },
1888
1955
  render(ctx) {
1889
1956
  const { renderer, camera } = ctx;
1890
1957
  const gl = renderer.gl;
1891
1958
  const progress = ctx.animationProgress;
1892
- const meshProg = renderer.getProgram("mesh");
1893
- meshProg.use();
1894
- meshProg.setMat4("u_projView", camera.projViewMatrix);
1895
- meshProg.setMat4("u_model", modelMatrix);
1959
+ const prog = renderer.getProgram("mesh");
1960
+ prog.use();
1961
+ prog.setMat4("u_projView", camera.projViewMatrix);
1962
+ prog.setMat4("u_model", modelMatrix);
1896
1963
  mat3NormalFromMat4(normalMatrix, modelMatrix);
1897
- meshProg.setMat3("u_normalMatrix", normalMatrix);
1898
- setLightUniforms(meshProg, defaultLightConfig(), camera.position);
1899
- meshProg.setFloat("u_opacity", progress * 0.9);
1964
+ prog.setMat3("u_normalMatrix", normalMatrix);
1965
+ setLightUniforms(prog, defaultLightConfig(), camera.position);
1966
+ prog.setFloat("u_opacity", progress);
1900
1967
  if (sphereVBO && sphereIBO) {
1901
1968
  sphereVBO.bind();
1902
- const ml = createVertexLayout([
1903
- { location: meshProg.attributes["a_position"], size: 3 },
1904
- { location: meshProg.attributes["a_normal"], size: 3 },
1905
- { location: meshProg.attributes["a_color"], size: 3 }
1969
+ const layout = createVertexLayout([
1970
+ { location: prog.attributes["a_position"], size: 3 },
1971
+ { location: prog.attributes["a_normal"], size: 3 },
1972
+ { location: prog.attributes["a_color"], size: 3 }
1906
1973
  ]);
1907
- applyVertexLayout(gl, ml);
1974
+ applyVertexLayout(gl, layout);
1908
1975
  sphereIBO.bind();
1909
- gl.drawElements(gl.TRIANGLES, sphereIndexCount, gl.UNSIGNED_SHORT, 0);
1910
- disableVertexLayout(gl, ml);
1976
+ const indexType = gl.UNSIGNED_SHORT;
1977
+ gl.drawElements(gl.TRIANGLES, sphereIndexCount, indexType, 0);
1978
+ disableVertexLayout(gl, layout);
1911
1979
  }
1912
- const pointProg = renderer.getProgram("point3d");
1913
- if (pointProg && pointVBO && pointCount > 0) {
1914
- pointProg.use();
1915
- pointProg.setMat4("u_projView", camera.projViewMatrix);
1916
- pointProg.setMat4("u_model", modelMatrix);
1917
- pointProg.setFloat("u_pixelRatio", renderer.pixelRatio);
1918
- pointProg.setFloat("u_sizeAttenuation", 30);
1919
- pointProg.setFloat("u_opacity", progress);
1920
- pointVBO.bind();
1921
- const pl = createVertexLayout([
1922
- { location: pointProg.attributes["a_position"], size: 3 },
1923
- { location: pointProg.attributes["a_color"], size: 3 },
1924
- { location: pointProg.attributes["a_size"], size: 1 }
1925
- ]);
1926
- applyVertexLayout(gl, pl);
1927
- gl.drawArrays(gl.POINTS, 0, pointCount);
1928
- disableVertexLayout(gl, pl);
1980
+ },
1981
+ renderOverlay(ctx, ctx2d) {
1982
+ const { camera, width, height, theme, animationProgress } = ctx;
1983
+ if (animationProgress < 0.5) return;
1984
+ ctx2d.save();
1985
+ const seenLabels = /* @__PURE__ */ new Set();
1986
+ ctx2d.font = `${theme.fontSize - 1}px ${theme.fontFamily}`;
1987
+ ctx2d.textBaseline = "middle";
1988
+ for (const sp of screenPoints) {
1989
+ if (!sp.label || seenLabels.has(sp.label)) continue;
1990
+ const screen = projectToScreen(
1991
+ new Float32Array([sp.wx, sp.wy, sp.wz]),
1992
+ camera.projViewMatrix,
1993
+ width,
1994
+ height
1995
+ );
1996
+ if (!screen || screen.z < -1 || screen.z > 1 || screen.z > 0.97) continue;
1997
+ seenLabels.add(sp.label);
1998
+ ctx2d.fillStyle = theme.textColor;
1999
+ ctx2d.globalAlpha = 0.7;
2000
+ ctx2d.beginPath();
2001
+ ctx2d.arc(screen.x, screen.y, 2.5, 0, Math.PI * 2);
2002
+ ctx2d.fill();
2003
+ ctx2d.fillStyle = theme.textColor;
2004
+ ctx2d.globalAlpha = 0.85;
2005
+ ctx2d.textAlign = "left";
2006
+ ctx2d.fillText(sp.label, screen.x + 8, screen.y);
2007
+ }
2008
+ if (seriesInfo.length > 1) {
2009
+ const lx = 12, ly = height - 14 - seriesInfo.length * 18;
2010
+ ctx2d.fillStyle = "rgba(0,0,0,0.45)";
2011
+ ctx2d.globalAlpha = 1;
2012
+ ctx2d.beginPath();
2013
+ ctx2d.roundRect(lx - 6, ly - 8, 120, seriesInfo.length * 18 + 12, 6);
2014
+ ctx2d.fill();
2015
+ ctx2d.textAlign = "left";
2016
+ ctx2d.textBaseline = "top";
2017
+ ctx2d.font = `${theme.fontSize - 1}px ${theme.fontFamily}`;
2018
+ for (let i = 0; i < seriesInfo.length; i++) {
2019
+ const s = seriesInfo[i];
2020
+ const y = ly + i * 18;
2021
+ ctx2d.fillStyle = s.color;
2022
+ ctx2d.globalAlpha = 0.9;
2023
+ ctx2d.beginPath();
2024
+ ctx2d.arc(lx + 4, y + 6, 4, 0, Math.PI * 2);
2025
+ ctx2d.fill();
2026
+ ctx2d.fillStyle = "rgba(255,255,255,0.8)";
2027
+ ctx2d.fillText(s.name, lx + 14, y);
2028
+ }
1929
2029
  }
2030
+ ctx2d.restore();
1930
2031
  },
1931
2032
  needsLoop() {
1932
2033
  return false;
@@ -1934,14 +2035,26 @@ function createGlobe3DPlugin() {
1934
2035
  hitTest(ctx, x, y) {
1935
2036
  const { camera, width, height } = ctx;
1936
2037
  let closest = null;
1937
- let closestDist = 20;
1938
- for (const gp of globePoints) {
1939
- const screen = projectToScreen(new Float32Array([gp.wx, gp.wy, gp.wz]), camera.projViewMatrix, width, height);
2038
+ let closestDist = 25;
2039
+ for (const sp of screenPoints) {
2040
+ const screen = projectToScreen(
2041
+ new Float32Array([sp.wx, sp.wy, sp.wz]),
2042
+ camera.projViewMatrix,
2043
+ width,
2044
+ height
2045
+ );
1940
2046
  if (!screen || screen.z < -1 || screen.z > 1) continue;
1941
2047
  const dist = Math.sqrt((screen.x - x) ** 2 + (screen.y - y) ** 2);
1942
2048
  if (dist < closestDist) {
1943
2049
  closestDist = dist;
1944
- closest = { seriesIndex: gp.si, dataIndex: gp.di, value: gp.value, x: gp.lng, y: gp.lat, seriesName: gp.name };
2050
+ closest = {
2051
+ seriesIndex: sp.si,
2052
+ dataIndex: sp.di,
2053
+ value: sp.value,
2054
+ x: sp.lng,
2055
+ y: sp.lat,
2056
+ seriesName: sp.label || sp.name
2057
+ };
1945
2058
  }
1946
2059
  }
1947
2060
  return closest;
@@ -1951,9 +2064,7 @@ function createGlobe3DPlugin() {
1951
2064
  sphereVBO = null;
1952
2065
  sphereIBO?.destroy();
1953
2066
  sphereIBO = null;
1954
- pointVBO?.destroy();
1955
- pointVBO = null;
1956
- globePoints = [];
2067
+ screenPoints = [];
1957
2068
  }
1958
2069
  };
1959
2070
  }
@@ -3512,5 +3623,5 @@ exports.toRad = toRad;
3512
3623
  exports.updateCamera = updateCamera;
3513
3624
  exports.updateOrbitControls = updateOrbitControls;
3514
3625
  exports.vec3 = vec3;
3515
- //# sourceMappingURL=chunk-Q4JAQOV3.cjs.map
3516
- //# sourceMappingURL=chunk-Q4JAQOV3.cjs.map
3626
+ //# sourceMappingURL=chunk-JCECG2KY.cjs.map
3627
+ //# sourceMappingURL=chunk-JCECG2KY.cjs.map