@chartts/gl 0.1.3
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/LICENSE +21 -0
- package/dist/bar3d.cjs +20 -0
- package/dist/bar3d.cjs.map +1 -0
- package/dist/bar3d.d.cts +10 -0
- package/dist/bar3d.d.ts +10 -0
- package/dist/bar3d.js +3 -0
- package/dist/bar3d.js.map +1 -0
- package/dist/chunk-M24XMYGG.js +3464 -0
- package/dist/chunk-M24XMYGG.js.map +1 -0
- package/dist/chunk-Q4JAQOV3.cjs +3516 -0
- package/dist/chunk-Q4JAQOV3.cjs.map +1 -0
- package/dist/factory-Cp9Kr7aa.d.cts +291 -0
- package/dist/factory-Cp9Kr7aa.d.ts +291 -0
- package/dist/flow-gl.cjs +20 -0
- package/dist/flow-gl.cjs.map +1 -0
- package/dist/flow-gl.d.cts +20 -0
- package/dist/flow-gl.d.ts +20 -0
- package/dist/flow-gl.js +3 -0
- package/dist/flow-gl.js.map +1 -0
- package/dist/globe3d.cjs +20 -0
- package/dist/globe3d.cjs.map +1 -0
- package/dist/globe3d.d.cts +10 -0
- package/dist/globe3d.d.ts +10 -0
- package/dist/globe3d.js +3 -0
- package/dist/globe3d.js.map +1 -0
- package/dist/graph-gl.cjs +20 -0
- package/dist/graph-gl.cjs.map +1 -0
- package/dist/graph-gl.d.cts +10 -0
- package/dist/graph-gl.d.ts +10 -0
- package/dist/graph-gl.js +3 -0
- package/dist/graph-gl.js.map +1 -0
- package/dist/index.cjs +212 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +60 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/line3d.cjs +20 -0
- package/dist/line3d.cjs.map +1 -0
- package/dist/line3d.d.cts +10 -0
- package/dist/line3d.d.ts +10 -0
- package/dist/line3d.js +3 -0
- package/dist/line3d.js.map +1 -0
- package/dist/lines-gl.cjs +20 -0
- package/dist/lines-gl.cjs.map +1 -0
- package/dist/lines-gl.d.cts +10 -0
- package/dist/lines-gl.d.ts +10 -0
- package/dist/lines-gl.js +3 -0
- package/dist/lines-gl.js.map +1 -0
- package/dist/lines3d.cjs +20 -0
- package/dist/lines3d.cjs.map +1 -0
- package/dist/lines3d.d.cts +10 -0
- package/dist/lines3d.d.ts +10 -0
- package/dist/lines3d.js +3 -0
- package/dist/lines3d.js.map +1 -0
- package/dist/map3d.cjs +20 -0
- package/dist/map3d.cjs.map +1 -0
- package/dist/map3d.d.cts +10 -0
- package/dist/map3d.d.ts +10 -0
- package/dist/map3d.js +3 -0
- package/dist/map3d.js.map +1 -0
- package/dist/scatter-gl.cjs +20 -0
- package/dist/scatter-gl.cjs.map +1 -0
- package/dist/scatter-gl.d.cts +11 -0
- package/dist/scatter-gl.d.ts +11 -0
- package/dist/scatter-gl.js +3 -0
- package/dist/scatter-gl.js.map +1 -0
- package/dist/scatter3d.cjs +20 -0
- package/dist/scatter3d.cjs.map +1 -0
- package/dist/scatter3d.d.cts +10 -0
- package/dist/scatter3d.d.ts +10 -0
- package/dist/scatter3d.js +3 -0
- package/dist/scatter3d.js.map +1 -0
- package/dist/surface3d.cjs +20 -0
- package/dist/surface3d.cjs.map +1 -0
- package/dist/surface3d.d.cts +10 -0
- package/dist/surface3d.d.ts +10 -0
- package/dist/surface3d.js +3 -0
- package/dist/surface3d.js.map +1 -0
- package/package.json +92 -0
|
@@ -0,0 +1,3516 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/engine/math.ts
|
|
4
|
+
function vec3(x = 0, y = 0, z = 0) {
|
|
5
|
+
return new Float32Array([x, y, z]);
|
|
6
|
+
}
|
|
7
|
+
function mat4() {
|
|
8
|
+
const m = new Float32Array(16);
|
|
9
|
+
m[0] = 1;
|
|
10
|
+
m[5] = 1;
|
|
11
|
+
m[10] = 1;
|
|
12
|
+
m[15] = 1;
|
|
13
|
+
return m;
|
|
14
|
+
}
|
|
15
|
+
function vec3Add(out, a, b) {
|
|
16
|
+
out[0] = a[0] + b[0];
|
|
17
|
+
out[1] = a[1] + b[1];
|
|
18
|
+
out[2] = a[2] + b[2];
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
function vec3Scale(out, a, s) {
|
|
22
|
+
out[0] = a[0] * s;
|
|
23
|
+
out[1] = a[1] * s;
|
|
24
|
+
out[2] = a[2] * s;
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
function vec3Length(a) {
|
|
28
|
+
return Math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
|
|
29
|
+
}
|
|
30
|
+
function vec3Normalize(out, a) {
|
|
31
|
+
const len = vec3Length(a);
|
|
32
|
+
if (len > 0) {
|
|
33
|
+
const inv = 1 / len;
|
|
34
|
+
out[0] = a[0] * inv;
|
|
35
|
+
out[1] = a[1] * inv;
|
|
36
|
+
out[2] = a[2] * inv;
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
function vec3Cross(out, a, b) {
|
|
41
|
+
const ax = a[0], ay = a[1], az = a[2];
|
|
42
|
+
const bx = b[0], by = b[1], bz = b[2];
|
|
43
|
+
out[0] = ay * bz - az * by;
|
|
44
|
+
out[1] = az * bx - ax * bz;
|
|
45
|
+
out[2] = ax * by - ay * bx;
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
function mat4Identity(out) {
|
|
49
|
+
out.fill(0);
|
|
50
|
+
out[0] = 1;
|
|
51
|
+
out[5] = 1;
|
|
52
|
+
out[10] = 1;
|
|
53
|
+
out[15] = 1;
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
function mat4Multiply(out, a, b) {
|
|
57
|
+
const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
|
|
58
|
+
const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
|
|
59
|
+
const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
|
|
60
|
+
const a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
|
|
61
|
+
let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
|
|
62
|
+
out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
|
|
63
|
+
out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
|
|
64
|
+
out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
|
|
65
|
+
out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
|
|
66
|
+
b0 = b[4];
|
|
67
|
+
b1 = b[5];
|
|
68
|
+
b2 = b[6];
|
|
69
|
+
b3 = b[7];
|
|
70
|
+
out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
|
|
71
|
+
out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
|
|
72
|
+
out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
|
|
73
|
+
out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
|
|
74
|
+
b0 = b[8];
|
|
75
|
+
b1 = b[9];
|
|
76
|
+
b2 = b[10];
|
|
77
|
+
b3 = b[11];
|
|
78
|
+
out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
|
|
79
|
+
out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
|
|
80
|
+
out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
|
|
81
|
+
out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
|
|
82
|
+
b0 = b[12];
|
|
83
|
+
b1 = b[13];
|
|
84
|
+
b2 = b[14];
|
|
85
|
+
b3 = b[15];
|
|
86
|
+
out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
|
|
87
|
+
out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
|
|
88
|
+
out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
|
|
89
|
+
out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
function mat4Perspective(out, fovY, aspect, near, far) {
|
|
93
|
+
const f = 1 / Math.tan(fovY / 2);
|
|
94
|
+
out.fill(0);
|
|
95
|
+
out[0] = f / aspect;
|
|
96
|
+
out[5] = f;
|
|
97
|
+
out[10] = (far + near) / (near - far);
|
|
98
|
+
out[11] = -1;
|
|
99
|
+
out[14] = 2 * far * near / (near - far);
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
function mat4Ortho(out, left, right, bottom, top, near, far) {
|
|
103
|
+
const lr = 1 / (left - right);
|
|
104
|
+
const bt = 1 / (bottom - top);
|
|
105
|
+
const nf = 1 / (near - far);
|
|
106
|
+
out.fill(0);
|
|
107
|
+
out[0] = -2 * lr;
|
|
108
|
+
out[5] = -2 * bt;
|
|
109
|
+
out[10] = 2 * nf;
|
|
110
|
+
out[12] = (left + right) * lr;
|
|
111
|
+
out[13] = (top + bottom) * bt;
|
|
112
|
+
out[14] = (far + near) * nf;
|
|
113
|
+
out[15] = 1;
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
function mat4LookAt(out, eye, center, up) {
|
|
117
|
+
const zx = eye[0] - center[0];
|
|
118
|
+
const zy = eye[1] - center[1];
|
|
119
|
+
const zz = eye[2] - center[2];
|
|
120
|
+
let len = 1 / Math.sqrt(zx * zx + zy * zy + zz * zz);
|
|
121
|
+
const z0 = zx * len, z1 = zy * len, z2 = zz * len;
|
|
122
|
+
const xx = up[1] * z2 - up[2] * z1;
|
|
123
|
+
const xy = up[2] * z0 - up[0] * z2;
|
|
124
|
+
const xz = up[0] * z1 - up[1] * z0;
|
|
125
|
+
len = Math.sqrt(xx * xx + xy * xy + xz * xz);
|
|
126
|
+
const x0 = len ? xx / len : 0, x1 = len ? xy / len : 0, x2 = len ? xz / len : 0;
|
|
127
|
+
const y0 = z1 * x2 - z2 * x1;
|
|
128
|
+
const y1 = z2 * x0 - z0 * x2;
|
|
129
|
+
const y2 = z0 * x1 - z1 * x0;
|
|
130
|
+
out[0] = x0;
|
|
131
|
+
out[1] = y0;
|
|
132
|
+
out[2] = z0;
|
|
133
|
+
out[3] = 0;
|
|
134
|
+
out[4] = x1;
|
|
135
|
+
out[5] = y1;
|
|
136
|
+
out[6] = z1;
|
|
137
|
+
out[7] = 0;
|
|
138
|
+
out[8] = x2;
|
|
139
|
+
out[9] = y2;
|
|
140
|
+
out[10] = z2;
|
|
141
|
+
out[11] = 0;
|
|
142
|
+
out[12] = -(x0 * eye[0] + x1 * eye[1] + x2 * eye[2]);
|
|
143
|
+
out[13] = -(y0 * eye[0] + y1 * eye[1] + y2 * eye[2]);
|
|
144
|
+
out[14] = -(z0 * eye[0] + z1 * eye[1] + z2 * eye[2]);
|
|
145
|
+
out[15] = 1;
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
function mat4Invert(out, a) {
|
|
149
|
+
const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
|
|
150
|
+
const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
|
|
151
|
+
const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
|
|
152
|
+
const a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
|
|
153
|
+
const b00 = a00 * a11 - a01 * a10;
|
|
154
|
+
const b01 = a00 * a12 - a02 * a10;
|
|
155
|
+
const b02 = a00 * a13 - a03 * a10;
|
|
156
|
+
const b03 = a01 * a12 - a02 * a11;
|
|
157
|
+
const b04 = a01 * a13 - a03 * a11;
|
|
158
|
+
const b05 = a02 * a13 - a03 * a12;
|
|
159
|
+
const b06 = a20 * a31 - a21 * a30;
|
|
160
|
+
const b07 = a20 * a32 - a22 * a30;
|
|
161
|
+
const b08 = a20 * a33 - a23 * a30;
|
|
162
|
+
const b09 = a21 * a32 - a22 * a31;
|
|
163
|
+
const b10 = a21 * a33 - a23 * a31;
|
|
164
|
+
const b11 = a22 * a33 - a23 * a32;
|
|
165
|
+
let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
|
|
166
|
+
if (!det) return null;
|
|
167
|
+
det = 1 / det;
|
|
168
|
+
out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
|
|
169
|
+
out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
|
|
170
|
+
out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
|
|
171
|
+
out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
|
|
172
|
+
out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
|
|
173
|
+
out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
|
|
174
|
+
out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
|
|
175
|
+
out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
|
|
176
|
+
out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
|
|
177
|
+
out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
|
|
178
|
+
out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
|
|
179
|
+
out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
|
|
180
|
+
out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
|
|
181
|
+
out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
|
|
182
|
+
out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
|
|
183
|
+
out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
function mat3NormalFromMat4(out, a) {
|
|
187
|
+
const a00 = a[0], a01 = a[1], a02 = a[2];
|
|
188
|
+
const a10 = a[4], a11 = a[5], a12 = a[6];
|
|
189
|
+
const a20 = a[8], a21 = a[9], a22 = a[10];
|
|
190
|
+
const b01 = a22 * a11 - a12 * a21;
|
|
191
|
+
const b11 = -a22 * a10 + a12 * a20;
|
|
192
|
+
const b21 = a21 * a10 - a11 * a20;
|
|
193
|
+
let det = a00 * b01 + a01 * b11 + a02 * b21;
|
|
194
|
+
if (!det) return out;
|
|
195
|
+
det = 1 / det;
|
|
196
|
+
out[0] = b01 * det;
|
|
197
|
+
out[1] = (-a22 * a01 + a02 * a21) * det;
|
|
198
|
+
out[2] = (a12 * a01 - a02 * a11) * det;
|
|
199
|
+
out[3] = b11 * det;
|
|
200
|
+
out[4] = (a22 * a00 - a02 * a20) * det;
|
|
201
|
+
out[5] = (-a12 * a00 + a02 * a10) * det;
|
|
202
|
+
out[6] = b21 * det;
|
|
203
|
+
out[7] = (-a21 * a00 + a01 * a20) * det;
|
|
204
|
+
out[8] = (a11 * a00 - a01 * a10) * det;
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
function toRad(deg) {
|
|
208
|
+
return deg * Math.PI / 180;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/engine/camera.ts
|
|
212
|
+
function createCamera(width, height, opts = {}) {
|
|
213
|
+
const state = {
|
|
214
|
+
position: vec3(...opts.position ?? [5, 5, 5]),
|
|
215
|
+
target: vec3(...opts.target ?? [0, 0, 0]),
|
|
216
|
+
up: vec3(...opts.up ?? [0, 1, 0]),
|
|
217
|
+
fov: toRad(opts.fov ?? 45),
|
|
218
|
+
near: opts.near ?? 0.1,
|
|
219
|
+
far: opts.far ?? 1e3,
|
|
220
|
+
aspect: width / height,
|
|
221
|
+
ortho: opts.ortho ?? false,
|
|
222
|
+
projectionMatrix: mat4(),
|
|
223
|
+
viewMatrix: mat4(),
|
|
224
|
+
projViewMatrix: mat4(),
|
|
225
|
+
invProjViewMatrix: mat4()
|
|
226
|
+
};
|
|
227
|
+
updateCamera(state, width, height);
|
|
228
|
+
return state;
|
|
229
|
+
}
|
|
230
|
+
function updateCamera(state, width, height) {
|
|
231
|
+
state.aspect = width / height;
|
|
232
|
+
if (state.ortho) {
|
|
233
|
+
const halfH = 5;
|
|
234
|
+
const halfW = halfH * state.aspect;
|
|
235
|
+
mat4Ortho(state.projectionMatrix, -halfW, halfW, -halfH, halfH, state.near, state.far);
|
|
236
|
+
} else {
|
|
237
|
+
mat4Perspective(state.projectionMatrix, state.fov, state.aspect, state.near, state.far);
|
|
238
|
+
}
|
|
239
|
+
mat4LookAt(state.viewMatrix, state.position, state.target, state.up);
|
|
240
|
+
mat4Multiply(state.projViewMatrix, state.projectionMatrix, state.viewMatrix);
|
|
241
|
+
mat4Invert(state.invProjViewMatrix, state.projViewMatrix);
|
|
242
|
+
}
|
|
243
|
+
function setCameraPosition(state, pos) {
|
|
244
|
+
state.position[0] = pos[0];
|
|
245
|
+
state.position[1] = pos[1];
|
|
246
|
+
state.position[2] = pos[2];
|
|
247
|
+
}
|
|
248
|
+
function setCameraTarget(state, target) {
|
|
249
|
+
state.target[0] = target[0];
|
|
250
|
+
state.target[1] = target[1];
|
|
251
|
+
state.target[2] = target[2];
|
|
252
|
+
}
|
|
253
|
+
function projectToScreen(worldPos, projView, width, height) {
|
|
254
|
+
const m = projView;
|
|
255
|
+
const x = worldPos[0], y = worldPos[1], z = worldPos[2];
|
|
256
|
+
const w = m[3] * x + m[7] * y + m[11] * z + m[15];
|
|
257
|
+
if (Math.abs(w) < 1e-10) return null;
|
|
258
|
+
const ndcX = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w;
|
|
259
|
+
const ndcY = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w;
|
|
260
|
+
const ndcZ = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w;
|
|
261
|
+
return {
|
|
262
|
+
x: (ndcX * 0.5 + 0.5) * width,
|
|
263
|
+
y: (1 - (ndcY * 0.5 + 0.5)) * height,
|
|
264
|
+
z: ndcZ
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/engine/orbit-controls.ts
|
|
269
|
+
function cartesianToSpherical(pos, target) {
|
|
270
|
+
const dx = pos[0] - target[0];
|
|
271
|
+
const dy = pos[1] - target[1];
|
|
272
|
+
const dz = pos[2] - target[2];
|
|
273
|
+
const radius = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
274
|
+
const theta = Math.atan2(dx, dz);
|
|
275
|
+
const phi = Math.acos(Math.max(-1, Math.min(1, dy / (radius || 1))));
|
|
276
|
+
return { theta, phi, radius };
|
|
277
|
+
}
|
|
278
|
+
function sphericalToCartesian(theta, phi, radius, target, out) {
|
|
279
|
+
out[0] = target[0] + radius * Math.sin(phi) * Math.sin(theta);
|
|
280
|
+
out[1] = target[1] + radius * Math.cos(phi);
|
|
281
|
+
out[2] = target[2] + radius * Math.sin(phi) * Math.cos(theta);
|
|
282
|
+
}
|
|
283
|
+
function createOrbitControls(canvas, camera, _width, _height, config = {}) {
|
|
284
|
+
const cfg = {
|
|
285
|
+
enableRotate: config.enableRotate ?? true,
|
|
286
|
+
enableZoom: config.enableZoom ?? true,
|
|
287
|
+
enablePan: config.enablePan ?? true,
|
|
288
|
+
damping: config.damping ?? 0.1,
|
|
289
|
+
autoRotate: config.autoRotate ?? false,
|
|
290
|
+
autoRotateSpeed: config.autoRotateSpeed ?? 1,
|
|
291
|
+
minDistance: config.minDistance ?? 0.5,
|
|
292
|
+
maxDistance: config.maxDistance ?? 500,
|
|
293
|
+
minPolarAngle: config.minPolarAngle ?? 0.01,
|
|
294
|
+
maxPolarAngle: config.maxPolarAngle ?? Math.PI - 0.01
|
|
295
|
+
};
|
|
296
|
+
const { theta, phi, radius } = cartesianToSpherical(camera.position, camera.target);
|
|
297
|
+
const state = {
|
|
298
|
+
theta,
|
|
299
|
+
phi,
|
|
300
|
+
radius,
|
|
301
|
+
target: vec3(camera.target[0], camera.target[1], camera.target[2]),
|
|
302
|
+
dragging: false,
|
|
303
|
+
panning: false,
|
|
304
|
+
lastX: 0,
|
|
305
|
+
lastY: 0,
|
|
306
|
+
velocityTheta: 0,
|
|
307
|
+
velocityPhi: 0,
|
|
308
|
+
config: cfg,
|
|
309
|
+
dispose: () => {
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
const onMouseDown = (e) => {
|
|
313
|
+
if (e.button === 0 && cfg.enableRotate) {
|
|
314
|
+
state.dragging = true;
|
|
315
|
+
state.lastX = e.clientX;
|
|
316
|
+
state.lastY = e.clientY;
|
|
317
|
+
state.velocityTheta = 0;
|
|
318
|
+
state.velocityPhi = 0;
|
|
319
|
+
} else if (e.button === 2 && cfg.enablePan) {
|
|
320
|
+
state.panning = true;
|
|
321
|
+
state.lastX = e.clientX;
|
|
322
|
+
state.lastY = e.clientY;
|
|
323
|
+
e.preventDefault();
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const onMouseMove = (e) => {
|
|
327
|
+
if (state.dragging) {
|
|
328
|
+
const dx = e.clientX - state.lastX;
|
|
329
|
+
const dy = e.clientY - state.lastY;
|
|
330
|
+
state.velocityTheta = -dx * 5e-3;
|
|
331
|
+
state.velocityPhi = -dy * 5e-3;
|
|
332
|
+
state.theta += state.velocityTheta;
|
|
333
|
+
state.phi = Math.max(cfg.minPolarAngle, Math.min(cfg.maxPolarAngle, state.phi + state.velocityPhi));
|
|
334
|
+
state.lastX = e.clientX;
|
|
335
|
+
state.lastY = e.clientY;
|
|
336
|
+
} else if (state.panning) {
|
|
337
|
+
const dx = e.clientX - state.lastX;
|
|
338
|
+
const dy = e.clientY - state.lastY;
|
|
339
|
+
const panSpeed = state.radius * 2e-3;
|
|
340
|
+
const forward = vec3(
|
|
341
|
+
camera.target[0] - camera.position[0],
|
|
342
|
+
camera.target[1] - camera.position[1],
|
|
343
|
+
camera.target[2] - camera.position[2]
|
|
344
|
+
);
|
|
345
|
+
const right = vec3(0, 0, 0);
|
|
346
|
+
vec3Cross(right, forward, camera.up);
|
|
347
|
+
vec3Normalize(right, right);
|
|
348
|
+
const up = vec3(0, 0, 0);
|
|
349
|
+
vec3Cross(up, right, forward);
|
|
350
|
+
vec3Normalize(up, up);
|
|
351
|
+
const panX = vec3Scale(vec3(0, 0, 0), right, -dx * panSpeed);
|
|
352
|
+
const panY = vec3Scale(vec3(0, 0, 0), up, dy * panSpeed);
|
|
353
|
+
vec3Add(state.target, state.target, panX);
|
|
354
|
+
vec3Add(state.target, state.target, panY);
|
|
355
|
+
state.lastX = e.clientX;
|
|
356
|
+
state.lastY = e.clientY;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
const onMouseUp = () => {
|
|
360
|
+
state.dragging = false;
|
|
361
|
+
state.panning = false;
|
|
362
|
+
};
|
|
363
|
+
const onWheel = (e) => {
|
|
364
|
+
if (!cfg.enableZoom) return;
|
|
365
|
+
e.preventDefault();
|
|
366
|
+
const factor = e.deltaY > 0 ? 1.1 : 0.9;
|
|
367
|
+
state.radius = Math.max(cfg.minDistance, Math.min(cfg.maxDistance, state.radius * factor));
|
|
368
|
+
};
|
|
369
|
+
const onContextMenu = (e) => e.preventDefault();
|
|
370
|
+
let touchStartDist = 0;
|
|
371
|
+
let touchStartRadius = 0;
|
|
372
|
+
const onTouchStart = (e) => {
|
|
373
|
+
if (e.touches.length === 1 && cfg.enableRotate) {
|
|
374
|
+
state.dragging = true;
|
|
375
|
+
state.lastX = e.touches[0].clientX;
|
|
376
|
+
state.lastY = e.touches[0].clientY;
|
|
377
|
+
state.velocityTheta = 0;
|
|
378
|
+
state.velocityPhi = 0;
|
|
379
|
+
} else if (e.touches.length === 2 && cfg.enableZoom) {
|
|
380
|
+
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
|
381
|
+
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
|
382
|
+
touchStartDist = Math.sqrt(dx * dx + dy * dy);
|
|
383
|
+
touchStartRadius = state.radius;
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
const onTouchMove = (e) => {
|
|
387
|
+
e.preventDefault();
|
|
388
|
+
if (e.touches.length === 1 && state.dragging) {
|
|
389
|
+
const dx = e.touches[0].clientX - state.lastX;
|
|
390
|
+
const dy = e.touches[0].clientY - state.lastY;
|
|
391
|
+
state.velocityTheta = -dx * 5e-3;
|
|
392
|
+
state.velocityPhi = -dy * 5e-3;
|
|
393
|
+
state.theta += state.velocityTheta;
|
|
394
|
+
state.phi = Math.max(cfg.minPolarAngle, Math.min(cfg.maxPolarAngle, state.phi + state.velocityPhi));
|
|
395
|
+
state.lastX = e.touches[0].clientX;
|
|
396
|
+
state.lastY = e.touches[0].clientY;
|
|
397
|
+
} else if (e.touches.length === 2 && cfg.enableZoom) {
|
|
398
|
+
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
|
399
|
+
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
|
400
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
401
|
+
state.radius = Math.max(cfg.minDistance, Math.min(cfg.maxDistance, touchStartRadius * (touchStartDist / dist)));
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
const onTouchEnd = () => {
|
|
405
|
+
state.dragging = false;
|
|
406
|
+
};
|
|
407
|
+
canvas.addEventListener("mousedown", onMouseDown);
|
|
408
|
+
canvas.addEventListener("mousemove", onMouseMove);
|
|
409
|
+
canvas.addEventListener("mouseup", onMouseUp);
|
|
410
|
+
canvas.addEventListener("mouseleave", onMouseUp);
|
|
411
|
+
canvas.addEventListener("wheel", onWheel, { passive: false });
|
|
412
|
+
canvas.addEventListener("contextmenu", onContextMenu);
|
|
413
|
+
canvas.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
414
|
+
canvas.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
415
|
+
canvas.addEventListener("touchend", onTouchEnd);
|
|
416
|
+
state.dispose = () => {
|
|
417
|
+
canvas.removeEventListener("mousedown", onMouseDown);
|
|
418
|
+
canvas.removeEventListener("mousemove", onMouseMove);
|
|
419
|
+
canvas.removeEventListener("mouseup", onMouseUp);
|
|
420
|
+
canvas.removeEventListener("mouseleave", onMouseUp);
|
|
421
|
+
canvas.removeEventListener("wheel", onWheel);
|
|
422
|
+
canvas.removeEventListener("contextmenu", onContextMenu);
|
|
423
|
+
canvas.removeEventListener("touchstart", onTouchStart);
|
|
424
|
+
canvas.removeEventListener("touchmove", onTouchMove);
|
|
425
|
+
canvas.removeEventListener("touchend", onTouchEnd);
|
|
426
|
+
};
|
|
427
|
+
return state;
|
|
428
|
+
}
|
|
429
|
+
function updateOrbitControls(orbit, camera, width, height) {
|
|
430
|
+
let changed = false;
|
|
431
|
+
const cfg = orbit.config;
|
|
432
|
+
if (cfg.autoRotate && !orbit.dragging) {
|
|
433
|
+
orbit.theta += cfg.autoRotateSpeed * 1e-3;
|
|
434
|
+
changed = true;
|
|
435
|
+
}
|
|
436
|
+
if (!orbit.dragging) {
|
|
437
|
+
if (Math.abs(orbit.velocityTheta) > 1e-4 || Math.abs(orbit.velocityPhi) > 1e-4) {
|
|
438
|
+
orbit.theta += orbit.velocityTheta * cfg.damping;
|
|
439
|
+
orbit.phi += orbit.velocityPhi * cfg.damping;
|
|
440
|
+
orbit.phi = Math.max(cfg.minPolarAngle, Math.min(cfg.maxPolarAngle, orbit.phi));
|
|
441
|
+
orbit.velocityTheta *= 1 - cfg.damping;
|
|
442
|
+
orbit.velocityPhi *= 1 - cfg.damping;
|
|
443
|
+
changed = true;
|
|
444
|
+
} else {
|
|
445
|
+
orbit.velocityTheta = 0;
|
|
446
|
+
orbit.velocityPhi = 0;
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
changed = true;
|
|
450
|
+
}
|
|
451
|
+
if (orbit.panning) changed = true;
|
|
452
|
+
const newPos = vec3(0, 0, 0);
|
|
453
|
+
sphericalToCartesian(orbit.theta, orbit.phi, orbit.radius, orbit.target, newPos);
|
|
454
|
+
setCameraPosition(camera, newPos);
|
|
455
|
+
setCameraTarget(camera, orbit.target);
|
|
456
|
+
updateCamera(camera, width, height);
|
|
457
|
+
return changed;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/engine/shader.ts
|
|
461
|
+
function compileShader(gl, type, source) {
|
|
462
|
+
const shader = gl.createShader(type);
|
|
463
|
+
gl.shaderSource(shader, source);
|
|
464
|
+
gl.compileShader(shader);
|
|
465
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
466
|
+
const info = gl.getShaderInfoLog(shader);
|
|
467
|
+
gl.deleteShader(shader);
|
|
468
|
+
throw new Error(`[chartts/gl] Shader compile error: ${info}`);
|
|
469
|
+
}
|
|
470
|
+
return shader;
|
|
471
|
+
}
|
|
472
|
+
function createShaderProgram(gl, vertSrc, fragSrc, uniformNames, attributeNames) {
|
|
473
|
+
const vert = compileShader(gl, gl.VERTEX_SHADER, vertSrc);
|
|
474
|
+
const frag = compileShader(gl, gl.FRAGMENT_SHADER, fragSrc);
|
|
475
|
+
const program = gl.createProgram();
|
|
476
|
+
gl.attachShader(program, vert);
|
|
477
|
+
gl.attachShader(program, frag);
|
|
478
|
+
gl.linkProgram(program);
|
|
479
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
480
|
+
const info = gl.getProgramInfoLog(program);
|
|
481
|
+
gl.deleteProgram(program);
|
|
482
|
+
throw new Error(`[chartts/gl] Program link error: ${info}`);
|
|
483
|
+
}
|
|
484
|
+
const uniforms = {};
|
|
485
|
+
for (const name of uniformNames) {
|
|
486
|
+
const loc = gl.getUniformLocation(program, name);
|
|
487
|
+
if (loc) uniforms[name] = loc;
|
|
488
|
+
}
|
|
489
|
+
const attributes = {};
|
|
490
|
+
for (const name of attributeNames) {
|
|
491
|
+
attributes[name] = gl.getAttribLocation(program, name);
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
program,
|
|
495
|
+
uniforms,
|
|
496
|
+
attributes,
|
|
497
|
+
use() {
|
|
498
|
+
gl.useProgram(program);
|
|
499
|
+
},
|
|
500
|
+
setMat4(name, value) {
|
|
501
|
+
const loc = uniforms[name];
|
|
502
|
+
if (loc) gl.uniformMatrix4fv(loc, false, value);
|
|
503
|
+
},
|
|
504
|
+
setMat3(name, value) {
|
|
505
|
+
const loc = uniforms[name];
|
|
506
|
+
if (loc) gl.uniformMatrix3fv(loc, false, value);
|
|
507
|
+
},
|
|
508
|
+
setVec2(name, x, y) {
|
|
509
|
+
const loc = uniforms[name];
|
|
510
|
+
if (loc) gl.uniform2f(loc, x, y);
|
|
511
|
+
},
|
|
512
|
+
setVec3(name, x, y, z) {
|
|
513
|
+
const loc = uniforms[name];
|
|
514
|
+
if (loc) gl.uniform3f(loc, x, y, z);
|
|
515
|
+
},
|
|
516
|
+
setVec4(name, x, y, z, w) {
|
|
517
|
+
const loc = uniforms[name];
|
|
518
|
+
if (loc) gl.uniform4f(loc, x, y, z, w);
|
|
519
|
+
},
|
|
520
|
+
setFloat(name, value) {
|
|
521
|
+
const loc = uniforms[name];
|
|
522
|
+
if (loc) gl.uniform1f(loc, value);
|
|
523
|
+
},
|
|
524
|
+
setInt(name, value) {
|
|
525
|
+
const loc = uniforms[name];
|
|
526
|
+
if (loc) gl.uniform1i(loc, value);
|
|
527
|
+
},
|
|
528
|
+
destroy() {
|
|
529
|
+
gl.deleteShader(vert);
|
|
530
|
+
gl.deleteShader(frag);
|
|
531
|
+
gl.deleteProgram(program);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/engine/renderer.ts
|
|
537
|
+
function createGLRenderer(container) {
|
|
538
|
+
const pixelRatio = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
539
|
+
const glCanvas = document.createElement("canvas");
|
|
540
|
+
glCanvas.style.position = "absolute";
|
|
541
|
+
glCanvas.style.top = "0";
|
|
542
|
+
glCanvas.style.left = "0";
|
|
543
|
+
glCanvas.style.width = "100%";
|
|
544
|
+
glCanvas.style.height = "100%";
|
|
545
|
+
const overlayCanvas = document.createElement("canvas");
|
|
546
|
+
overlayCanvas.style.position = "absolute";
|
|
547
|
+
overlayCanvas.style.top = "0";
|
|
548
|
+
overlayCanvas.style.left = "0";
|
|
549
|
+
overlayCanvas.style.width = "100%";
|
|
550
|
+
overlayCanvas.style.height = "100%";
|
|
551
|
+
overlayCanvas.style.pointerEvents = "none";
|
|
552
|
+
const containerPos = getComputedStyle(container).position;
|
|
553
|
+
if (containerPos === "static") container.style.position = "relative";
|
|
554
|
+
container.appendChild(glCanvas);
|
|
555
|
+
container.appendChild(overlayCanvas);
|
|
556
|
+
const gl = glCanvas.getContext("webgl", {
|
|
557
|
+
antialias: true,
|
|
558
|
+
alpha: false,
|
|
559
|
+
premultipliedAlpha: false,
|
|
560
|
+
preserveDrawingBuffer: false,
|
|
561
|
+
depth: true,
|
|
562
|
+
stencil: false
|
|
563
|
+
});
|
|
564
|
+
if (!gl) throw new Error("[chartts/gl] WebGL not supported");
|
|
565
|
+
const ctx2d = overlayCanvas.getContext("2d");
|
|
566
|
+
gl.enable(gl.DEPTH_TEST);
|
|
567
|
+
gl.depthFunc(gl.LEQUAL);
|
|
568
|
+
gl.enable(gl.CULL_FACE);
|
|
569
|
+
gl.cullFace(gl.BACK);
|
|
570
|
+
gl.enable(gl.BLEND);
|
|
571
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
572
|
+
const width = container.clientWidth;
|
|
573
|
+
const height = container.clientHeight;
|
|
574
|
+
const renderer = {
|
|
575
|
+
gl,
|
|
576
|
+
ctx2d,
|
|
577
|
+
glCanvas,
|
|
578
|
+
overlayCanvas,
|
|
579
|
+
width,
|
|
580
|
+
height,
|
|
581
|
+
pixelRatio,
|
|
582
|
+
programs: /* @__PURE__ */ new Map(),
|
|
583
|
+
registerProgram(name, vert, frag, uniforms, attributes) {
|
|
584
|
+
const existing = this.programs.get(name);
|
|
585
|
+
if (existing) return existing;
|
|
586
|
+
const prog = createShaderProgram(gl, vert, frag, uniforms, attributes);
|
|
587
|
+
this.programs.set(name, prog);
|
|
588
|
+
return prog;
|
|
589
|
+
},
|
|
590
|
+
getProgram(name) {
|
|
591
|
+
return this.programs.get(name);
|
|
592
|
+
},
|
|
593
|
+
resize(w, h) {
|
|
594
|
+
this.width = w;
|
|
595
|
+
this.height = h;
|
|
596
|
+
const pw = Math.round(w * pixelRatio);
|
|
597
|
+
const ph = Math.round(h * pixelRatio);
|
|
598
|
+
glCanvas.width = pw;
|
|
599
|
+
glCanvas.height = ph;
|
|
600
|
+
overlayCanvas.width = pw;
|
|
601
|
+
overlayCanvas.height = ph;
|
|
602
|
+
gl.viewport(0, 0, pw, ph);
|
|
603
|
+
ctx2d.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
|
604
|
+
},
|
|
605
|
+
beginFrame() {
|
|
606
|
+
ctx2d.clearRect(0, 0, this.width, this.height);
|
|
607
|
+
},
|
|
608
|
+
endFrame() {
|
|
609
|
+
gl.flush();
|
|
610
|
+
},
|
|
611
|
+
clear(r, g, b, a) {
|
|
612
|
+
gl.clearColor(r, g, b, a);
|
|
613
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
614
|
+
},
|
|
615
|
+
destroy() {
|
|
616
|
+
for (const prog of this.programs.values()) {
|
|
617
|
+
prog.destroy();
|
|
618
|
+
}
|
|
619
|
+
this.programs.clear();
|
|
620
|
+
glCanvas.remove();
|
|
621
|
+
overlayCanvas.remove();
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
renderer.resize(width, height);
|
|
625
|
+
return renderer;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/engine/lighting.ts
|
|
629
|
+
function defaultLightConfig() {
|
|
630
|
+
return {
|
|
631
|
+
ambient: [0.35, 0.33, 0.4],
|
|
632
|
+
diffuseDirection: [0.6, 0.9, 0.4],
|
|
633
|
+
diffuseColor: [1, 0.97, 0.95],
|
|
634
|
+
specularColor: [1, 0.98, 0.96],
|
|
635
|
+
shininess: 48
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function setLightUniforms(program, light, cameraPos) {
|
|
639
|
+
program.setVec3("u_ambientColor", light.ambient[0], light.ambient[1], light.ambient[2]);
|
|
640
|
+
const [dx, dy, dz] = light.diffuseDirection;
|
|
641
|
+
const len = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
|
|
642
|
+
program.setVec3("u_lightDir", dx / len, dy / len, dz / len);
|
|
643
|
+
program.setVec3("u_diffuseColor", light.diffuseColor[0], light.diffuseColor[1], light.diffuseColor[2]);
|
|
644
|
+
program.setVec3("u_specularColor", light.specularColor[0], light.specularColor[1], light.specularColor[2]);
|
|
645
|
+
program.setFloat("u_shininess", light.shininess);
|
|
646
|
+
program.setVec3("u_cameraPos", cameraPos[0], cameraPos[1], cameraPos[2]);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// src/engine/picking.ts
|
|
650
|
+
function createPickingSystem(gl, width, height, pixelRatio) {
|
|
651
|
+
const pw = Math.round(width * pixelRatio);
|
|
652
|
+
const ph = Math.round(height * pixelRatio);
|
|
653
|
+
const framebuffer = gl.createFramebuffer();
|
|
654
|
+
const texture = gl.createTexture();
|
|
655
|
+
const depthBuffer = gl.createRenderbuffer();
|
|
656
|
+
function setupFBO(w, h) {
|
|
657
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
658
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
659
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
660
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
661
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
662
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
663
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
|
|
664
|
+
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h);
|
|
665
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
666
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
667
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
|
|
668
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
669
|
+
}
|
|
670
|
+
setupFBO(pw, ph);
|
|
671
|
+
let currentWidth = pw;
|
|
672
|
+
let currentHeight = ph;
|
|
673
|
+
const pixel = new Uint8Array(4);
|
|
674
|
+
return {
|
|
675
|
+
begin() {
|
|
676
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
677
|
+
gl.viewport(0, 0, currentWidth, currentHeight);
|
|
678
|
+
gl.clearColor(0, 0, 0, 0);
|
|
679
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
680
|
+
gl.disable(gl.BLEND);
|
|
681
|
+
},
|
|
682
|
+
end() {
|
|
683
|
+
gl.enable(gl.BLEND);
|
|
684
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
685
|
+
gl.viewport(0, 0, currentWidth, currentHeight);
|
|
686
|
+
},
|
|
687
|
+
pick(x, y) {
|
|
688
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
689
|
+
const px = Math.round(x * pixelRatio);
|
|
690
|
+
const py = currentHeight - Math.round(y * pixelRatio);
|
|
691
|
+
gl.readPixels(px, py, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
|
|
692
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
693
|
+
if (pixel[3] === 0) return -1;
|
|
694
|
+
return pixel[0] + pixel[1] * 256 + pixel[2] * 65536;
|
|
695
|
+
},
|
|
696
|
+
idToColor(id) {
|
|
697
|
+
return [
|
|
698
|
+
(id & 255) / 255,
|
|
699
|
+
(id >> 8 & 255) / 255,
|
|
700
|
+
(id >> 16 & 255) / 255
|
|
701
|
+
];
|
|
702
|
+
},
|
|
703
|
+
resize(w, h) {
|
|
704
|
+
currentWidth = Math.round(w * pixelRatio);
|
|
705
|
+
currentHeight = Math.round(h * pixelRatio);
|
|
706
|
+
setupFBO(currentWidth, currentHeight);
|
|
707
|
+
},
|
|
708
|
+
destroy() {
|
|
709
|
+
gl.deleteFramebuffer(framebuffer);
|
|
710
|
+
gl.deleteTexture(texture);
|
|
711
|
+
gl.deleteRenderbuffer(depthBuffer);
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// src/engine/buffer.ts
|
|
717
|
+
function createVertexBuffer(gl, data, usage = gl.STATIC_DRAW) {
|
|
718
|
+
const buffer = gl.createBuffer();
|
|
719
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
720
|
+
gl.bufferData(gl.ARRAY_BUFFER, data, usage);
|
|
721
|
+
return {
|
|
722
|
+
buffer,
|
|
723
|
+
count: data.length,
|
|
724
|
+
bind() {
|
|
725
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
726
|
+
},
|
|
727
|
+
update(newData, u = usage) {
|
|
728
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
729
|
+
gl.bufferData(gl.ARRAY_BUFFER, newData, u);
|
|
730
|
+
this.count = newData.length;
|
|
731
|
+
},
|
|
732
|
+
destroy() {
|
|
733
|
+
gl.deleteBuffer(buffer);
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
function createIndexBuffer(gl, data, usage = gl.STATIC_DRAW) {
|
|
738
|
+
const buffer = gl.createBuffer();
|
|
739
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
|
|
740
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, usage);
|
|
741
|
+
return {
|
|
742
|
+
buffer,
|
|
743
|
+
count: data.length,
|
|
744
|
+
bind() {
|
|
745
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
|
|
746
|
+
},
|
|
747
|
+
update(newData, u = usage) {
|
|
748
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
|
|
749
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, newData, u);
|
|
750
|
+
this.count = newData.length;
|
|
751
|
+
},
|
|
752
|
+
destroy() {
|
|
753
|
+
gl.deleteBuffer(buffer);
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
function createVertexLayout(attribs) {
|
|
758
|
+
let offset = 0;
|
|
759
|
+
const attributes = [];
|
|
760
|
+
for (const a of attribs) {
|
|
761
|
+
attributes.push({ location: a.location, size: a.size, offset: offset * 4 });
|
|
762
|
+
offset += a.size;
|
|
763
|
+
}
|
|
764
|
+
return { stride: offset * 4, attributes };
|
|
765
|
+
}
|
|
766
|
+
function applyVertexLayout(gl, layout) {
|
|
767
|
+
for (const attr of layout.attributes) {
|
|
768
|
+
gl.enableVertexAttribArray(attr.location);
|
|
769
|
+
gl.vertexAttribPointer(attr.location, attr.size, gl.FLOAT, false, layout.stride, attr.offset);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function disableVertexLayout(gl, layout) {
|
|
773
|
+
for (const attr of layout.attributes) {
|
|
774
|
+
gl.disableVertexAttribArray(attr.location);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// src/engine/grid3d.ts
|
|
779
|
+
var GRID_VERT = (
|
|
780
|
+
/* glsl */
|
|
781
|
+
`
|
|
782
|
+
precision highp float;
|
|
783
|
+
attribute vec3 a_position;
|
|
784
|
+
attribute vec3 a_color;
|
|
785
|
+
uniform mat4 u_projView;
|
|
786
|
+
varying vec3 v_color;
|
|
787
|
+
void main() {
|
|
788
|
+
gl_Position = u_projView * vec4(a_position, 1.0);
|
|
789
|
+
v_color = a_color;
|
|
790
|
+
}
|
|
791
|
+
`
|
|
792
|
+
);
|
|
793
|
+
var GRID_FRAG = (
|
|
794
|
+
/* glsl */
|
|
795
|
+
`
|
|
796
|
+
precision highp float;
|
|
797
|
+
uniform float u_opacity;
|
|
798
|
+
varying vec3 v_color;
|
|
799
|
+
void main() {
|
|
800
|
+
gl_FragColor = vec4(v_color, u_opacity);
|
|
801
|
+
}
|
|
802
|
+
`
|
|
803
|
+
);
|
|
804
|
+
function createGrid3D(renderer) {
|
|
805
|
+
const gl = renderer.gl;
|
|
806
|
+
renderer.registerProgram(
|
|
807
|
+
"grid3d",
|
|
808
|
+
GRID_VERT,
|
|
809
|
+
GRID_FRAG,
|
|
810
|
+
["u_projView", "u_opacity"],
|
|
811
|
+
["a_position", "a_color"]
|
|
812
|
+
);
|
|
813
|
+
let vbo = null;
|
|
814
|
+
let lineCount = 0;
|
|
815
|
+
return {
|
|
816
|
+
update(bounds) {
|
|
817
|
+
const { minX, maxX, minZ, maxZ, y } = bounds;
|
|
818
|
+
const rangeX = maxX - minX || 1;
|
|
819
|
+
const rangeZ = maxZ - minZ || 1;
|
|
820
|
+
const maxRange = Math.max(rangeX, rangeZ);
|
|
821
|
+
const rawStep = maxRange / 10;
|
|
822
|
+
const magnitude = Math.pow(10, Math.floor(Math.log10(rawStep)));
|
|
823
|
+
const normalized = rawStep / magnitude;
|
|
824
|
+
const step = normalized < 2 ? magnitude : normalized < 5 ? 2 * magnitude : 5 * magnitude;
|
|
825
|
+
const gridColor = [0.18, 0.2, 0.28];
|
|
826
|
+
const axisColor = [0.3, 0.35, 0.5];
|
|
827
|
+
const verts = [];
|
|
828
|
+
const pad = step;
|
|
829
|
+
const gMinX = Math.floor((minX - pad) / step) * step;
|
|
830
|
+
const gMaxX = Math.ceil((maxX + pad) / step) * step;
|
|
831
|
+
const gMinZ = Math.floor((minZ - pad) / step) * step;
|
|
832
|
+
const gMaxZ = Math.ceil((maxZ + pad) / step) * step;
|
|
833
|
+
for (let x = gMinX; x <= gMaxX + step * 0.01; x += step) {
|
|
834
|
+
const isAxis = Math.abs(x) < step * 0.01;
|
|
835
|
+
const c = isAxis ? axisColor : gridColor;
|
|
836
|
+
verts.push(x, y, gMinZ, c[0], c[1], c[2]);
|
|
837
|
+
verts.push(x, y, gMaxZ, c[0], c[1], c[2]);
|
|
838
|
+
}
|
|
839
|
+
for (let z = gMinZ; z <= gMaxZ + step * 0.01; z += step) {
|
|
840
|
+
const isAxis = Math.abs(z) < step * 0.01;
|
|
841
|
+
const c = isAxis ? axisColor : gridColor;
|
|
842
|
+
verts.push(gMinX, y, z, c[0], c[1], c[2]);
|
|
843
|
+
verts.push(gMaxX, y, z, c[0], c[1], c[2]);
|
|
844
|
+
}
|
|
845
|
+
const data = new Float32Array(verts);
|
|
846
|
+
if (vbo) vbo.update(data);
|
|
847
|
+
else vbo = createVertexBuffer(gl, data, gl.DYNAMIC_DRAW);
|
|
848
|
+
lineCount = verts.length / 6 / 2;
|
|
849
|
+
},
|
|
850
|
+
render(camera, opacity) {
|
|
851
|
+
if (!vbo || lineCount === 0) return;
|
|
852
|
+
const prog = renderer.getProgram("grid3d");
|
|
853
|
+
prog.use();
|
|
854
|
+
prog.setMat4("u_projView", camera.projViewMatrix);
|
|
855
|
+
prog.setFloat("u_opacity", opacity * 0.4);
|
|
856
|
+
vbo.bind();
|
|
857
|
+
const layout = createVertexLayout([
|
|
858
|
+
{ location: prog.attributes["a_position"], size: 3 },
|
|
859
|
+
{ location: prog.attributes["a_color"], size: 3 }
|
|
860
|
+
]);
|
|
861
|
+
applyVertexLayout(gl, layout);
|
|
862
|
+
gl.disable(gl.DEPTH_TEST);
|
|
863
|
+
gl.drawArrays(gl.LINES, 0, lineCount * 2);
|
|
864
|
+
gl.enable(gl.DEPTH_TEST);
|
|
865
|
+
disableVertexLayout(gl, layout);
|
|
866
|
+
},
|
|
867
|
+
destroy() {
|
|
868
|
+
vbo?.destroy();
|
|
869
|
+
vbo = null;
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/types.ts
|
|
875
|
+
var DEFAULT_GL_THEME = {
|
|
876
|
+
background: [0.06, 0.065, 0.1],
|
|
877
|
+
colors: [
|
|
878
|
+
"#6c9eff",
|
|
879
|
+
"#5eead4",
|
|
880
|
+
"#fbbf24",
|
|
881
|
+
"#f472b6",
|
|
882
|
+
"#a78bfa",
|
|
883
|
+
"#34d399",
|
|
884
|
+
"#fb923c",
|
|
885
|
+
"#38bdf8"
|
|
886
|
+
],
|
|
887
|
+
textColor: "#b8c4d0",
|
|
888
|
+
gridColor: "#1e2536",
|
|
889
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
890
|
+
fontSize: 12
|
|
891
|
+
};
|
|
892
|
+
var LIGHT_GL_THEME = {
|
|
893
|
+
background: [0.95, 0.96, 0.98],
|
|
894
|
+
colors: [
|
|
895
|
+
"#4f7df5",
|
|
896
|
+
"#10b981",
|
|
897
|
+
"#f59e0b",
|
|
898
|
+
"#ec4899",
|
|
899
|
+
"#8b5cf6",
|
|
900
|
+
"#14b8a6",
|
|
901
|
+
"#f97316",
|
|
902
|
+
"#0ea5e9"
|
|
903
|
+
],
|
|
904
|
+
textColor: "#374151",
|
|
905
|
+
gridColor: "#d1d9e6",
|
|
906
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
907
|
+
fontSize: 12
|
|
908
|
+
};
|
|
909
|
+
function hexToRGB(hex) {
|
|
910
|
+
const h = hex.replace("#", "");
|
|
911
|
+
return [
|
|
912
|
+
parseInt(h.substring(0, 2), 16) / 255,
|
|
913
|
+
parseInt(h.substring(2, 4), 16) / 255,
|
|
914
|
+
parseInt(h.substring(4, 6), 16) / 255
|
|
915
|
+
];
|
|
916
|
+
}
|
|
917
|
+
function resolveTheme(theme) {
|
|
918
|
+
if (!theme || theme === "dark") return DEFAULT_GL_THEME;
|
|
919
|
+
if (theme === "light") return LIGHT_GL_THEME;
|
|
920
|
+
return theme;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// src/api/create-gl.ts
|
|
924
|
+
function computeDataBounds(data) {
|
|
925
|
+
let minX = Infinity, maxX = -Infinity;
|
|
926
|
+
let minY = Infinity, maxY = -Infinity;
|
|
927
|
+
let minZ = Infinity, maxZ = -Infinity;
|
|
928
|
+
if (data.grid && data.grid.length > 0) {
|
|
929
|
+
const rows = data.grid.length, cols = data.grid[0].length;
|
|
930
|
+
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
|
|
931
|
+
const y = data.grid[r][c];
|
|
932
|
+
const x = c / (cols - 1 || 1) * 10 - 5;
|
|
933
|
+
const z = r / (rows - 1 || 1) * 10 - 5;
|
|
934
|
+
if (x < minX) minX = x;
|
|
935
|
+
if (x > maxX) maxX = x;
|
|
936
|
+
if (y < minY) minY = y;
|
|
937
|
+
if (y > maxY) maxY = y;
|
|
938
|
+
if (z < minZ) minZ = z;
|
|
939
|
+
if (z > maxZ) maxZ = z;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
for (const s of data.series) {
|
|
943
|
+
const s3d = s;
|
|
944
|
+
if (s3d.x) for (const v of s3d.x) {
|
|
945
|
+
if (v < minX) minX = v;
|
|
946
|
+
if (v > maxX) maxX = v;
|
|
947
|
+
}
|
|
948
|
+
if (s3d.y) for (const v of s3d.y) {
|
|
949
|
+
if (v < minY) minY = v;
|
|
950
|
+
if (v > maxY) maxY = v;
|
|
951
|
+
}
|
|
952
|
+
if (s3d.z) for (const v of s3d.z) {
|
|
953
|
+
if (v < minZ) minZ = v;
|
|
954
|
+
if (v > maxZ) maxZ = v;
|
|
955
|
+
}
|
|
956
|
+
if (s3d.values) for (const v of s3d.values) {
|
|
957
|
+
if (v < minY) minY = v;
|
|
958
|
+
if (v > maxY) maxY = v;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (minX === Infinity) {
|
|
962
|
+
minX = -5;
|
|
963
|
+
maxX = 5;
|
|
964
|
+
}
|
|
965
|
+
if (minY === Infinity) {
|
|
966
|
+
minY = 0;
|
|
967
|
+
maxY = 5;
|
|
968
|
+
}
|
|
969
|
+
if (minZ === Infinity) {
|
|
970
|
+
minZ = -5;
|
|
971
|
+
maxZ = 5;
|
|
972
|
+
}
|
|
973
|
+
const cx = (minX + maxX) / 2;
|
|
974
|
+
const cy = (minY + maxY) / 2;
|
|
975
|
+
const cz = (minZ + maxZ) / 2;
|
|
976
|
+
const extentX = maxX - minX || 1;
|
|
977
|
+
const extentY = maxY - minY || 1;
|
|
978
|
+
const extentZ = maxZ - minZ || 1;
|
|
979
|
+
const maxExtent = Math.max(extentX, extentY, extentZ);
|
|
980
|
+
return { center: [cx, cy, cz], maxExtent, minX, maxX, minY, maxY, minZ, maxZ };
|
|
981
|
+
}
|
|
982
|
+
function createGLChart(container, plugin, data, options = {}) {
|
|
983
|
+
const el = typeof container === "string" ? document.querySelector(container) : container;
|
|
984
|
+
if (!el) throw new Error(`[chartts/gl] Container not found: ${container}`);
|
|
985
|
+
const renderer = createGLRenderer(el);
|
|
986
|
+
const { width, height } = renderer;
|
|
987
|
+
const theme = resolveTheme(options.theme);
|
|
988
|
+
const is2D = plugin.type.endsWith("-gl");
|
|
989
|
+
let cameraOpts = options.camera;
|
|
990
|
+
if (!is2D && !cameraOpts?.position) {
|
|
991
|
+
const bounds = computeDataBounds(data);
|
|
992
|
+
const [cx, cy, cz] = bounds.center;
|
|
993
|
+
const dist = Math.max(bounds.maxExtent * 1.6, 5);
|
|
994
|
+
cameraOpts = {
|
|
995
|
+
...cameraOpts,
|
|
996
|
+
position: [cx + dist * 0.7, cy + dist * 0.5, cz + dist * 0.7],
|
|
997
|
+
target: [cx, cy, cz]
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
const camera = createCamera(width, height, cameraOpts);
|
|
1001
|
+
let orbit = null;
|
|
1002
|
+
if (options.orbit !== false) {
|
|
1003
|
+
const orbitConfig = typeof options.orbit === "object" ? options.orbit : {};
|
|
1004
|
+
orbit = createOrbitControls(renderer.glCanvas, camera, width, height, orbitConfig);
|
|
1005
|
+
}
|
|
1006
|
+
const lightConfig = {
|
|
1007
|
+
...defaultLightConfig(),
|
|
1008
|
+
...options.light || {}
|
|
1009
|
+
};
|
|
1010
|
+
const picking = createPickingSystem(renderer.gl, width, height, renderer.pixelRatio);
|
|
1011
|
+
const ctx = {
|
|
1012
|
+
renderer,
|
|
1013
|
+
camera,
|
|
1014
|
+
orbit,
|
|
1015
|
+
picking,
|
|
1016
|
+
width,
|
|
1017
|
+
height,
|
|
1018
|
+
data,
|
|
1019
|
+
options,
|
|
1020
|
+
theme,
|
|
1021
|
+
animationProgress: options.animate !== false ? 0 : 1
|
|
1022
|
+
};
|
|
1023
|
+
plugin.prepare(ctx);
|
|
1024
|
+
let grid = null;
|
|
1025
|
+
if (!is2D && options["grid"] !== false) {
|
|
1026
|
+
grid = createGrid3D(renderer);
|
|
1027
|
+
const bounds = computeDataBounds(data);
|
|
1028
|
+
grid.update({
|
|
1029
|
+
minX: bounds.minX,
|
|
1030
|
+
maxX: bounds.maxX,
|
|
1031
|
+
minZ: bounds.minZ,
|
|
1032
|
+
maxZ: bounds.maxZ,
|
|
1033
|
+
y: bounds.minY
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
let animating = options.animate !== false;
|
|
1037
|
+
const animDuration = options.animationDuration ?? 800;
|
|
1038
|
+
let animStart = 0;
|
|
1039
|
+
let rafId = 0;
|
|
1040
|
+
let disposed = false;
|
|
1041
|
+
let loopRunning = false;
|
|
1042
|
+
let dirty = true;
|
|
1043
|
+
let tooltipEl = null;
|
|
1044
|
+
if (options.tooltip !== false) {
|
|
1045
|
+
tooltipEl = document.createElement("div");
|
|
1046
|
+
tooltipEl.style.cssText = `
|
|
1047
|
+
position: absolute; pointer-events: none; display: none;
|
|
1048
|
+
background: rgba(0,0,0,0.85); color: #fff; padding: 6px 10px;
|
|
1049
|
+
border-radius: 4px; font-size: 12px; font-family: ${theme.fontFamily};
|
|
1050
|
+
z-index: 10; white-space: nowrap;
|
|
1051
|
+
`;
|
|
1052
|
+
el.appendChild(tooltipEl);
|
|
1053
|
+
}
|
|
1054
|
+
function drawFrame() {
|
|
1055
|
+
const bg = theme.background;
|
|
1056
|
+
renderer.beginFrame();
|
|
1057
|
+
renderer.clear(bg[0], bg[1], bg[2], 1);
|
|
1058
|
+
const meshProg = renderer.getProgram("mesh");
|
|
1059
|
+
if (meshProg) {
|
|
1060
|
+
meshProg.use();
|
|
1061
|
+
setLightUniforms(meshProg, lightConfig, camera.position);
|
|
1062
|
+
}
|
|
1063
|
+
if (grid) grid.render(camera, ctx.animationProgress);
|
|
1064
|
+
plugin.render(ctx);
|
|
1065
|
+
if (plugin.renderOverlay) {
|
|
1066
|
+
plugin.renderOverlay(ctx, renderer.ctx2d);
|
|
1067
|
+
}
|
|
1068
|
+
renderer.endFrame();
|
|
1069
|
+
}
|
|
1070
|
+
function renderFrame(time) {
|
|
1071
|
+
if (disposed) {
|
|
1072
|
+
loopRunning = false;
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
if (animating) {
|
|
1076
|
+
if (animStart === 0) animStart = time;
|
|
1077
|
+
const elapsed = time - animStart;
|
|
1078
|
+
ctx.animationProgress = Math.min(1, elapsed / animDuration);
|
|
1079
|
+
if (ctx.animationProgress >= 1) animating = false;
|
|
1080
|
+
dirty = true;
|
|
1081
|
+
}
|
|
1082
|
+
if (orbit) {
|
|
1083
|
+
const orbitChanged = updateOrbitControls(orbit, camera, ctx.width, ctx.height);
|
|
1084
|
+
if (orbitChanged) dirty = true;
|
|
1085
|
+
}
|
|
1086
|
+
const needsLoop = plugin.needsLoop?.(ctx) ?? false;
|
|
1087
|
+
if (needsLoop) dirty = true;
|
|
1088
|
+
if (dirty) {
|
|
1089
|
+
drawFrame();
|
|
1090
|
+
dirty = false;
|
|
1091
|
+
}
|
|
1092
|
+
const keepLooping = animating || needsLoop || orbit != null && (orbit.dragging || orbit.panning || Math.abs(orbit.velocityTheta) > 1e-4 || Math.abs(orbit.velocityPhi) > 1e-4 || orbit.config.autoRotate);
|
|
1093
|
+
if (keepLooping) {
|
|
1094
|
+
rafId = requestAnimationFrame(renderFrame);
|
|
1095
|
+
} else {
|
|
1096
|
+
loopRunning = false;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
function ensureLoop() {
|
|
1100
|
+
if (disposed) return;
|
|
1101
|
+
dirty = true;
|
|
1102
|
+
if (!loopRunning) {
|
|
1103
|
+
loopRunning = true;
|
|
1104
|
+
rafId = requestAnimationFrame(renderFrame);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
const onInteraction = () => ensureLoop();
|
|
1108
|
+
renderer.glCanvas.addEventListener("mousedown", onInteraction);
|
|
1109
|
+
renderer.glCanvas.addEventListener("mousemove", onInteraction);
|
|
1110
|
+
renderer.glCanvas.addEventListener("wheel", onInteraction, { passive: true });
|
|
1111
|
+
renderer.glCanvas.addEventListener("touchstart", onInteraction, { passive: true });
|
|
1112
|
+
renderer.glCanvas.addEventListener("touchmove", onInteraction, { passive: true });
|
|
1113
|
+
const onMouseMove = (e) => {
|
|
1114
|
+
const rect = renderer.glCanvas.getBoundingClientRect();
|
|
1115
|
+
const x = e.clientX - rect.left;
|
|
1116
|
+
const y = e.clientY - rect.top;
|
|
1117
|
+
const hit = plugin.hitTest(ctx, x, y);
|
|
1118
|
+
if (tooltipEl) {
|
|
1119
|
+
if (hit) {
|
|
1120
|
+
tooltipEl.style.display = "block";
|
|
1121
|
+
tooltipEl.style.left = `${x + 12}px`;
|
|
1122
|
+
tooltipEl.style.top = `${y - 12}px`;
|
|
1123
|
+
tooltipEl.textContent = `${hit.seriesName}: ${hit.value.toFixed(2)}`;
|
|
1124
|
+
} else {
|
|
1125
|
+
tooltipEl.style.display = "none";
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
renderer.glCanvas.addEventListener("mousemove", onMouseMove);
|
|
1130
|
+
loopRunning = true;
|
|
1131
|
+
rafId = requestAnimationFrame(renderFrame);
|
|
1132
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
1133
|
+
if (disposed) return;
|
|
1134
|
+
const w = el.clientWidth;
|
|
1135
|
+
const h = el.clientHeight;
|
|
1136
|
+
if (w === 0 || h === 0) return;
|
|
1137
|
+
renderer.resize(w, h);
|
|
1138
|
+
ctx.width = w;
|
|
1139
|
+
ctx.height = h;
|
|
1140
|
+
updateCamera(camera, w, h);
|
|
1141
|
+
picking.resize(w, h);
|
|
1142
|
+
ensureLoop();
|
|
1143
|
+
});
|
|
1144
|
+
resizeObserver.observe(el);
|
|
1145
|
+
return {
|
|
1146
|
+
update(newData) {
|
|
1147
|
+
ctx.data = newData;
|
|
1148
|
+
plugin.prepare(ctx);
|
|
1149
|
+
if (grid) {
|
|
1150
|
+
const bounds = computeDataBounds(newData);
|
|
1151
|
+
grid.update({ minX: bounds.minX, maxX: bounds.maxX, minZ: bounds.minZ, maxZ: bounds.maxZ, y: bounds.minY });
|
|
1152
|
+
}
|
|
1153
|
+
if (options.animate !== false) {
|
|
1154
|
+
animating = true;
|
|
1155
|
+
animStart = 0;
|
|
1156
|
+
ctx.animationProgress = 0;
|
|
1157
|
+
}
|
|
1158
|
+
ensureLoop();
|
|
1159
|
+
},
|
|
1160
|
+
setOption(opts) {
|
|
1161
|
+
Object.assign(ctx.options, opts);
|
|
1162
|
+
if (opts.theme) {
|
|
1163
|
+
const newTheme = resolveTheme(opts.theme);
|
|
1164
|
+
Object.assign(ctx.theme, newTheme);
|
|
1165
|
+
}
|
|
1166
|
+
plugin.prepare(ctx);
|
|
1167
|
+
ensureLoop();
|
|
1168
|
+
},
|
|
1169
|
+
setCameraPosition(pos) {
|
|
1170
|
+
camera.position[0] = pos[0];
|
|
1171
|
+
camera.position[1] = pos[1];
|
|
1172
|
+
camera.position[2] = pos[2];
|
|
1173
|
+
updateCamera(camera, ctx.width, ctx.height);
|
|
1174
|
+
ensureLoop();
|
|
1175
|
+
},
|
|
1176
|
+
setCameraTarget(target) {
|
|
1177
|
+
camera.target[0] = target[0];
|
|
1178
|
+
camera.target[1] = target[1];
|
|
1179
|
+
camera.target[2] = target[2];
|
|
1180
|
+
if (orbit) {
|
|
1181
|
+
orbit.target[0] = target[0];
|
|
1182
|
+
orbit.target[1] = target[1];
|
|
1183
|
+
orbit.target[2] = target[2];
|
|
1184
|
+
}
|
|
1185
|
+
updateCamera(camera, ctx.width, ctx.height);
|
|
1186
|
+
ensureLoop();
|
|
1187
|
+
},
|
|
1188
|
+
resize() {
|
|
1189
|
+
const w = el.clientWidth;
|
|
1190
|
+
const h = el.clientHeight;
|
|
1191
|
+
renderer.resize(w, h);
|
|
1192
|
+
ctx.width = w;
|
|
1193
|
+
ctx.height = h;
|
|
1194
|
+
updateCamera(camera, w, h);
|
|
1195
|
+
picking.resize(w, h);
|
|
1196
|
+
ensureLoop();
|
|
1197
|
+
},
|
|
1198
|
+
getDataAtPoint(x, y) {
|
|
1199
|
+
return plugin.hitTest(ctx, x, y);
|
|
1200
|
+
},
|
|
1201
|
+
destroy() {
|
|
1202
|
+
disposed = true;
|
|
1203
|
+
loopRunning = false;
|
|
1204
|
+
cancelAnimationFrame(rafId);
|
|
1205
|
+
resizeObserver.disconnect();
|
|
1206
|
+
renderer.glCanvas.removeEventListener("mousedown", onInteraction);
|
|
1207
|
+
renderer.glCanvas.removeEventListener("mousemove", onInteraction);
|
|
1208
|
+
renderer.glCanvas.removeEventListener("wheel", onInteraction);
|
|
1209
|
+
renderer.glCanvas.removeEventListener("touchstart", onInteraction);
|
|
1210
|
+
renderer.glCanvas.removeEventListener("touchmove", onInteraction);
|
|
1211
|
+
renderer.glCanvas.removeEventListener("mousemove", onMouseMove);
|
|
1212
|
+
if (tooltipEl) tooltipEl.remove();
|
|
1213
|
+
grid?.destroy();
|
|
1214
|
+
orbit?.dispose();
|
|
1215
|
+
plugin.dispose(renderer.gl);
|
|
1216
|
+
picking.destroy();
|
|
1217
|
+
renderer.destroy();
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// src/shaders/common.glsl.ts
|
|
1223
|
+
var PROJECTION_UNIFORMS = (
|
|
1224
|
+
/* glsl */
|
|
1225
|
+
`
|
|
1226
|
+
uniform mat4 u_projView;
|
|
1227
|
+
uniform mat4 u_model;
|
|
1228
|
+
`
|
|
1229
|
+
);
|
|
1230
|
+
var NORMAL_UNIFORMS = (
|
|
1231
|
+
/* glsl */
|
|
1232
|
+
`
|
|
1233
|
+
uniform mat3 u_normalMatrix;
|
|
1234
|
+
`
|
|
1235
|
+
);
|
|
1236
|
+
var PHONG_UNIFORMS = (
|
|
1237
|
+
/* glsl */
|
|
1238
|
+
`
|
|
1239
|
+
uniform vec3 u_ambientColor;
|
|
1240
|
+
uniform vec3 u_lightDir;
|
|
1241
|
+
uniform vec3 u_diffuseColor;
|
|
1242
|
+
uniform vec3 u_specularColor;
|
|
1243
|
+
uniform float u_shininess;
|
|
1244
|
+
uniform vec3 u_cameraPos;
|
|
1245
|
+
`
|
|
1246
|
+
);
|
|
1247
|
+
var PHONG_FUNCTION = (
|
|
1248
|
+
/* glsl */
|
|
1249
|
+
`
|
|
1250
|
+
vec3 phongLighting(vec3 normal, vec3 fragPos, vec3 baseColor) {
|
|
1251
|
+
vec3 N = normalize(normal);
|
|
1252
|
+
vec3 L = normalize(u_lightDir);
|
|
1253
|
+
vec3 V = normalize(u_cameraPos - fragPos);
|
|
1254
|
+
vec3 H = normalize(L + V);
|
|
1255
|
+
|
|
1256
|
+
// Hemisphere ambient - warm sky, cool ground
|
|
1257
|
+
float hemi = dot(N, vec3(0.0, 1.0, 0.0)) * 0.5 + 0.5;
|
|
1258
|
+
vec3 skyAmbient = u_ambientColor * vec3(1.1, 1.05, 1.2);
|
|
1259
|
+
vec3 groundAmbient = u_ambientColor * vec3(0.6, 0.55, 0.7);
|
|
1260
|
+
vec3 ambient = mix(groundAmbient, skyAmbient, hemi) * baseColor;
|
|
1261
|
+
|
|
1262
|
+
// Smooth wrapped diffuse for softer light falloff
|
|
1263
|
+
float NdotL = dot(N, L);
|
|
1264
|
+
float diff = max(NdotL * 0.5 + 0.5, 0.0);
|
|
1265
|
+
diff = diff * diff;
|
|
1266
|
+
vec3 diffuse = u_diffuseColor * diff * baseColor;
|
|
1267
|
+
|
|
1268
|
+
// Fill light from below-side for depth
|
|
1269
|
+
vec3 fillDir = normalize(vec3(-0.4, -0.3, -0.6));
|
|
1270
|
+
float fillDiff = max(dot(N, fillDir) * 0.5 + 0.5, 0.0);
|
|
1271
|
+
vec3 fill = baseColor * fillDiff * 0.15;
|
|
1272
|
+
|
|
1273
|
+
// Blinn-Phong specular with smooth falloff
|
|
1274
|
+
float spec = pow(max(dot(N, H), 0.0), u_shininess);
|
|
1275
|
+
vec3 specular = u_specularColor * spec * 0.7;
|
|
1276
|
+
|
|
1277
|
+
// Rim/fresnel glow - bright edges like subsurface scatter
|
|
1278
|
+
float rim = 1.0 - max(dot(V, N), 0.0);
|
|
1279
|
+
rim = pow(rim, 3.0);
|
|
1280
|
+
vec3 rimColor = mix(baseColor, vec3(1.0), 0.5);
|
|
1281
|
+
vec3 rimLight = rimColor * rim * 0.35;
|
|
1282
|
+
|
|
1283
|
+
vec3 result = ambient + diffuse + fill + specular + rimLight;
|
|
1284
|
+
|
|
1285
|
+
// Slight saturation boost
|
|
1286
|
+
float lum = dot(result, vec3(0.299, 0.587, 0.114));
|
|
1287
|
+
result = mix(vec3(lum), result, 1.12);
|
|
1288
|
+
|
|
1289
|
+
return clamp(result, 0.0, 1.0);
|
|
1290
|
+
}
|
|
1291
|
+
`
|
|
1292
|
+
);
|
|
1293
|
+
|
|
1294
|
+
// src/shaders/point.vert.ts
|
|
1295
|
+
var POINT_VERT = (
|
|
1296
|
+
/* glsl */
|
|
1297
|
+
`
|
|
1298
|
+
precision highp float;
|
|
1299
|
+
|
|
1300
|
+
${PROJECTION_UNIFORMS}
|
|
1301
|
+
|
|
1302
|
+
attribute vec3 a_position;
|
|
1303
|
+
attribute vec3 a_color;
|
|
1304
|
+
attribute float a_size;
|
|
1305
|
+
|
|
1306
|
+
uniform float u_pixelRatio;
|
|
1307
|
+
uniform float u_sizeAttenuation;
|
|
1308
|
+
|
|
1309
|
+
varying vec3 v_color;
|
|
1310
|
+
|
|
1311
|
+
void main() {
|
|
1312
|
+
vec4 worldPos = u_model * vec4(a_position, 1.0);
|
|
1313
|
+
vec4 mvPos = u_projView * worldPos;
|
|
1314
|
+
gl_Position = mvPos;
|
|
1315
|
+
|
|
1316
|
+
float attenuation = u_sizeAttenuation > 0.0
|
|
1317
|
+
? u_sizeAttenuation / max(mvPos.z, 0.1)
|
|
1318
|
+
: 1.0;
|
|
1319
|
+
gl_PointSize = a_size * u_pixelRatio * attenuation;
|
|
1320
|
+
v_color = a_color;
|
|
1321
|
+
}
|
|
1322
|
+
`
|
|
1323
|
+
);
|
|
1324
|
+
var POINT_VERT_UNIFORMS = [
|
|
1325
|
+
"u_projView",
|
|
1326
|
+
"u_model",
|
|
1327
|
+
"u_pixelRatio",
|
|
1328
|
+
"u_sizeAttenuation"
|
|
1329
|
+
];
|
|
1330
|
+
var POINT_VERT_ATTRIBUTES = [
|
|
1331
|
+
"a_position",
|
|
1332
|
+
"a_color",
|
|
1333
|
+
"a_size"
|
|
1334
|
+
];
|
|
1335
|
+
|
|
1336
|
+
// src/shaders/point.frag.ts
|
|
1337
|
+
var POINT_FRAG = (
|
|
1338
|
+
/* glsl */
|
|
1339
|
+
`
|
|
1340
|
+
precision highp float;
|
|
1341
|
+
|
|
1342
|
+
uniform float u_opacity;
|
|
1343
|
+
|
|
1344
|
+
varying vec3 v_color;
|
|
1345
|
+
|
|
1346
|
+
void main() {
|
|
1347
|
+
vec2 coord = gl_PointCoord - vec2(0.5);
|
|
1348
|
+
float dist = length(coord);
|
|
1349
|
+
if (dist > 0.5) discard;
|
|
1350
|
+
|
|
1351
|
+
// Solid core with soft edge
|
|
1352
|
+
float coreAlpha = 1.0 - smoothstep(0.3, 0.42, dist);
|
|
1353
|
+
// Outer glow halo
|
|
1354
|
+
float glowAlpha = (1.0 - smoothstep(0.2, 0.5, dist)) * 0.4;
|
|
1355
|
+
float alpha = max(coreAlpha, glowAlpha);
|
|
1356
|
+
|
|
1357
|
+
// Brighten center for a glossy look
|
|
1358
|
+
vec3 color = v_color + vec3(0.15) * (1.0 - smoothstep(0.0, 0.2, dist));
|
|
1359
|
+
|
|
1360
|
+
gl_FragColor = vec4(color, alpha * u_opacity);
|
|
1361
|
+
}
|
|
1362
|
+
`
|
|
1363
|
+
);
|
|
1364
|
+
var POINT_FRAG_UNIFORMS = ["u_opacity"];
|
|
1365
|
+
|
|
1366
|
+
// src/charts/scatter3d/scatter3d-type.ts
|
|
1367
|
+
function createScatter3DPlugin() {
|
|
1368
|
+
let vbo = null;
|
|
1369
|
+
let pointCount = 0;
|
|
1370
|
+
let seriesData = [];
|
|
1371
|
+
const modelMatrix = mat4();
|
|
1372
|
+
return {
|
|
1373
|
+
type: "scatter3d",
|
|
1374
|
+
prepare(ctx) {
|
|
1375
|
+
const { renderer, data, options, theme } = ctx;
|
|
1376
|
+
const gl = renderer.gl;
|
|
1377
|
+
const series = data.series;
|
|
1378
|
+
renderer.registerProgram(
|
|
1379
|
+
"point3d",
|
|
1380
|
+
POINT_VERT,
|
|
1381
|
+
POINT_FRAG,
|
|
1382
|
+
[...POINT_VERT_UNIFORMS, ...POINT_FRAG_UNIFORMS],
|
|
1383
|
+
POINT_VERT_ATTRIBUTES
|
|
1384
|
+
);
|
|
1385
|
+
const floatsPerVertex = 7;
|
|
1386
|
+
seriesData = [];
|
|
1387
|
+
let totalPoints = 0;
|
|
1388
|
+
for (const s of series) totalPoints += s.values.length;
|
|
1389
|
+
const vertices = new Float32Array(totalPoints * floatsPerVertex);
|
|
1390
|
+
let offset = 0;
|
|
1391
|
+
const defaultSize = options.pointSize ?? 8;
|
|
1392
|
+
for (let si = 0; si < series.length; si++) {
|
|
1393
|
+
const s = series[si];
|
|
1394
|
+
const color = hexToRGB(s.color ?? theme.colors[si % theme.colors.length]);
|
|
1395
|
+
const size = s.size ?? defaultSize;
|
|
1396
|
+
for (let di = 0; di < s.values.length; di++) {
|
|
1397
|
+
const x = s.x?.[di] ?? di;
|
|
1398
|
+
const y = s.values[di];
|
|
1399
|
+
const z = s.z?.[di] ?? 0;
|
|
1400
|
+
vertices[offset++] = x;
|
|
1401
|
+
vertices[offset++] = y;
|
|
1402
|
+
vertices[offset++] = z;
|
|
1403
|
+
vertices[offset++] = color[0];
|
|
1404
|
+
vertices[offset++] = color[1];
|
|
1405
|
+
vertices[offset++] = color[2];
|
|
1406
|
+
vertices[offset++] = size;
|
|
1407
|
+
seriesData.push({ seriesIndex: si, dataIndex: di, x, y, z, value: y, name: s.name });
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
if (vbo) vbo.update(vertices);
|
|
1411
|
+
else vbo = createVertexBuffer(gl, vertices, gl.DYNAMIC_DRAW);
|
|
1412
|
+
pointCount = totalPoints;
|
|
1413
|
+
mat4Identity(modelMatrix);
|
|
1414
|
+
},
|
|
1415
|
+
render(ctx) {
|
|
1416
|
+
const { renderer, camera } = ctx;
|
|
1417
|
+
const gl = renderer.gl;
|
|
1418
|
+
const prog = renderer.getProgram("point3d");
|
|
1419
|
+
prog.use();
|
|
1420
|
+
prog.setMat4("u_projView", camera.projViewMatrix);
|
|
1421
|
+
prog.setMat4("u_model", modelMatrix);
|
|
1422
|
+
prog.setFloat("u_pixelRatio", renderer.pixelRatio);
|
|
1423
|
+
prog.setFloat("u_sizeAttenuation", 50);
|
|
1424
|
+
prog.setFloat("u_opacity", ctx.animationProgress);
|
|
1425
|
+
if (!vbo || pointCount === 0) return;
|
|
1426
|
+
vbo.bind();
|
|
1427
|
+
const layout = createVertexLayout([
|
|
1428
|
+
{ location: prog.attributes["a_position"], size: 3 },
|
|
1429
|
+
{ location: prog.attributes["a_color"], size: 3 },
|
|
1430
|
+
{ location: prog.attributes["a_size"], size: 1 }
|
|
1431
|
+
]);
|
|
1432
|
+
applyVertexLayout(gl, layout);
|
|
1433
|
+
gl.drawArrays(gl.POINTS, 0, pointCount);
|
|
1434
|
+
disableVertexLayout(gl, layout);
|
|
1435
|
+
},
|
|
1436
|
+
hitTest(ctx, x, y) {
|
|
1437
|
+
const { camera, width, height } = ctx;
|
|
1438
|
+
let closest = null;
|
|
1439
|
+
let closestDist = 20;
|
|
1440
|
+
for (const d of seriesData) {
|
|
1441
|
+
const screen = projectToScreen(
|
|
1442
|
+
new Float32Array([d.x, d.y, d.z]),
|
|
1443
|
+
camera.projViewMatrix,
|
|
1444
|
+
width,
|
|
1445
|
+
height
|
|
1446
|
+
);
|
|
1447
|
+
if (!screen || screen.z < -1 || screen.z > 1) continue;
|
|
1448
|
+
const dx = screen.x - x;
|
|
1449
|
+
const dy = screen.y - y;
|
|
1450
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1451
|
+
if (dist < closestDist) {
|
|
1452
|
+
closestDist = dist;
|
|
1453
|
+
closest = { seriesIndex: d.seriesIndex, dataIndex: d.dataIndex, value: d.value, x: d.x, y: d.y, z: d.z, seriesName: d.name };
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
return closest;
|
|
1457
|
+
},
|
|
1458
|
+
dispose() {
|
|
1459
|
+
vbo?.destroy();
|
|
1460
|
+
vbo = null;
|
|
1461
|
+
seriesData = [];
|
|
1462
|
+
}
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// src/shaders/mesh.vert.ts
|
|
1467
|
+
var MESH_VERT = (
|
|
1468
|
+
/* glsl */
|
|
1469
|
+
`
|
|
1470
|
+
precision highp float;
|
|
1471
|
+
|
|
1472
|
+
${PROJECTION_UNIFORMS}
|
|
1473
|
+
${NORMAL_UNIFORMS}
|
|
1474
|
+
|
|
1475
|
+
attribute vec3 a_position;
|
|
1476
|
+
attribute vec3 a_normal;
|
|
1477
|
+
attribute vec3 a_color;
|
|
1478
|
+
|
|
1479
|
+
varying vec3 v_normal;
|
|
1480
|
+
varying vec3 v_fragPos;
|
|
1481
|
+
varying vec3 v_color;
|
|
1482
|
+
|
|
1483
|
+
void main() {
|
|
1484
|
+
vec4 worldPos = u_model * vec4(a_position, 1.0);
|
|
1485
|
+
v_fragPos = worldPos.xyz;
|
|
1486
|
+
v_normal = u_normalMatrix * a_normal;
|
|
1487
|
+
v_color = a_color;
|
|
1488
|
+
gl_Position = u_projView * worldPos;
|
|
1489
|
+
}
|
|
1490
|
+
`
|
|
1491
|
+
);
|
|
1492
|
+
var MESH_VERT_UNIFORMS = [
|
|
1493
|
+
"u_projView",
|
|
1494
|
+
"u_model",
|
|
1495
|
+
"u_normalMatrix"
|
|
1496
|
+
];
|
|
1497
|
+
var MESH_VERT_ATTRIBUTES = [
|
|
1498
|
+
"a_position",
|
|
1499
|
+
"a_normal",
|
|
1500
|
+
"a_color"
|
|
1501
|
+
];
|
|
1502
|
+
|
|
1503
|
+
// src/shaders/mesh.frag.ts
|
|
1504
|
+
var MESH_FRAG = (
|
|
1505
|
+
/* glsl */
|
|
1506
|
+
`
|
|
1507
|
+
precision highp float;
|
|
1508
|
+
|
|
1509
|
+
${PHONG_UNIFORMS}
|
|
1510
|
+
|
|
1511
|
+
uniform float u_opacity;
|
|
1512
|
+
|
|
1513
|
+
varying vec3 v_normal;
|
|
1514
|
+
varying vec3 v_fragPos;
|
|
1515
|
+
varying vec3 v_color;
|
|
1516
|
+
|
|
1517
|
+
${PHONG_FUNCTION}
|
|
1518
|
+
|
|
1519
|
+
void main() {
|
|
1520
|
+
vec3 lit = phongLighting(v_normal, v_fragPos, v_color);
|
|
1521
|
+
gl_FragColor = vec4(lit, u_opacity);
|
|
1522
|
+
}
|
|
1523
|
+
`
|
|
1524
|
+
);
|
|
1525
|
+
var MESH_FRAG_UNIFORMS = [
|
|
1526
|
+
"u_ambientColor",
|
|
1527
|
+
"u_lightDir",
|
|
1528
|
+
"u_diffuseColor",
|
|
1529
|
+
"u_specularColor",
|
|
1530
|
+
"u_shininess",
|
|
1531
|
+
"u_cameraPos",
|
|
1532
|
+
"u_opacity"
|
|
1533
|
+
];
|
|
1534
|
+
|
|
1535
|
+
// src/charts/bar3d/bar3d-type.ts
|
|
1536
|
+
function buildCuboid(cx, cy, cz, sx, sy, sz, r, g, b, verts, indices, baseVertex) {
|
|
1537
|
+
const hx = sx * 0.5, hy = sy * 0.5, hz = sz * 0.5;
|
|
1538
|
+
const faces = [
|
|
1539
|
+
{ p: [[1, 1, 1], [1, -1, 1], [1, -1, -1], [1, 1, -1]], n: [1, 0, 0] },
|
|
1540
|
+
{ p: [[-1, 1, -1], [-1, -1, -1], [-1, -1, 1], [-1, 1, 1]], n: [-1, 0, 0] },
|
|
1541
|
+
{ p: [[-1, 1, -1], [-1, 1, 1], [1, 1, 1], [1, 1, -1]], n: [0, 1, 0] },
|
|
1542
|
+
{ p: [[-1, -1, 1], [-1, -1, -1], [1, -1, -1], [1, -1, 1]], n: [0, -1, 0] },
|
|
1543
|
+
{ p: [[-1, 1, 1], [-1, -1, 1], [1, -1, 1], [1, 1, 1]], n: [0, 0, 1] },
|
|
1544
|
+
{ p: [[1, 1, -1], [1, -1, -1], [-1, -1, -1], [-1, 1, -1]], n: [0, 0, -1] }
|
|
1545
|
+
];
|
|
1546
|
+
let vi = baseVertex;
|
|
1547
|
+
for (const face of faces) {
|
|
1548
|
+
for (const p of face.p) {
|
|
1549
|
+
verts.push(cx + p[0] * hx, cy + p[1] * hy, cz + p[2] * hz, face.n[0], face.n[1], face.n[2], r, g, b);
|
|
1550
|
+
}
|
|
1551
|
+
indices.push(vi, vi + 1, vi + 2, vi, vi + 2, vi + 3);
|
|
1552
|
+
vi += 4;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
function createBar3DPlugin() {
|
|
1556
|
+
let vbo = null;
|
|
1557
|
+
let ibo = null;
|
|
1558
|
+
let indexCount = 0;
|
|
1559
|
+
let indexType = 0;
|
|
1560
|
+
const modelMatrix = mat4();
|
|
1561
|
+
const normalMatrix = new Float32Array(9);
|
|
1562
|
+
let barData = [];
|
|
1563
|
+
return {
|
|
1564
|
+
type: "bar3d",
|
|
1565
|
+
prepare(ctx) {
|
|
1566
|
+
const { renderer, data, options, theme } = ctx;
|
|
1567
|
+
const gl = renderer.gl;
|
|
1568
|
+
const series = data.series;
|
|
1569
|
+
renderer.registerProgram(
|
|
1570
|
+
"mesh",
|
|
1571
|
+
MESH_VERT,
|
|
1572
|
+
MESH_FRAG,
|
|
1573
|
+
[...MESH_VERT_UNIFORMS, ...MESH_FRAG_UNIFORMS],
|
|
1574
|
+
MESH_VERT_ATTRIBUTES
|
|
1575
|
+
);
|
|
1576
|
+
const verts = [];
|
|
1577
|
+
const indices = [];
|
|
1578
|
+
barData = [];
|
|
1579
|
+
let baseVertex = 0;
|
|
1580
|
+
const barWidth = options["barWidth"] ?? 0.6;
|
|
1581
|
+
const barDepth = options["barDepth"] ?? 0.6;
|
|
1582
|
+
for (let si = 0; si < series.length; si++) {
|
|
1583
|
+
const s = series[si];
|
|
1584
|
+
const color = hexToRGB(s.color ?? theme.colors[si % theme.colors.length]);
|
|
1585
|
+
for (let di = 0; di < s.values.length; di++) {
|
|
1586
|
+
const x = s.x?.[di] ?? di;
|
|
1587
|
+
const z = s.z?.[di] ?? si;
|
|
1588
|
+
const height = s.values[di];
|
|
1589
|
+
const cy = height * 0.5;
|
|
1590
|
+
buildCuboid(x, cy, z, barWidth, Math.max(0.01, height), barDepth, color[0], color[1], color[2], verts, indices, baseVertex);
|
|
1591
|
+
baseVertex += 24;
|
|
1592
|
+
barData.push({ si, di, cx: x, cy, cz: z, height, name: s.name, value: height });
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
const vertArr = new Float32Array(verts);
|
|
1596
|
+
const use32 = baseVertex > 65535;
|
|
1597
|
+
const idxArr = use32 ? new Uint32Array(indices) : new Uint16Array(indices);
|
|
1598
|
+
if (vbo) vbo.update(vertArr);
|
|
1599
|
+
else vbo = createVertexBuffer(gl, vertArr, gl.DYNAMIC_DRAW);
|
|
1600
|
+
if (ibo) ibo.update(idxArr);
|
|
1601
|
+
else ibo = createIndexBuffer(gl, idxArr, gl.DYNAMIC_DRAW);
|
|
1602
|
+
indexCount = indices.length;
|
|
1603
|
+
indexType = use32 ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT;
|
|
1604
|
+
mat4Identity(modelMatrix);
|
|
1605
|
+
},
|
|
1606
|
+
render(ctx) {
|
|
1607
|
+
const { renderer, camera } = ctx;
|
|
1608
|
+
const gl = renderer.gl;
|
|
1609
|
+
const prog = renderer.getProgram("mesh");
|
|
1610
|
+
const progress = ctx.animationProgress;
|
|
1611
|
+
prog.use();
|
|
1612
|
+
prog.setMat4("u_projView", camera.projViewMatrix);
|
|
1613
|
+
const animModel = mat4();
|
|
1614
|
+
mat4Identity(animModel);
|
|
1615
|
+
animModel[5] = progress;
|
|
1616
|
+
prog.setMat4("u_model", animModel);
|
|
1617
|
+
mat3NormalFromMat4(normalMatrix, animModel);
|
|
1618
|
+
prog.setMat3("u_normalMatrix", normalMatrix);
|
|
1619
|
+
setLightUniforms(prog, defaultLightConfig(), camera.position);
|
|
1620
|
+
prog.setFloat("u_opacity", 1);
|
|
1621
|
+
if (!vbo || !ibo || indexCount === 0) return;
|
|
1622
|
+
vbo.bind();
|
|
1623
|
+
const layout = createVertexLayout([
|
|
1624
|
+
{ location: prog.attributes["a_position"], size: 3 },
|
|
1625
|
+
{ location: prog.attributes["a_normal"], size: 3 },
|
|
1626
|
+
{ location: prog.attributes["a_color"], size: 3 }
|
|
1627
|
+
]);
|
|
1628
|
+
applyVertexLayout(gl, layout);
|
|
1629
|
+
ibo.bind();
|
|
1630
|
+
gl.drawElements(gl.TRIANGLES, indexCount, indexType, 0);
|
|
1631
|
+
disableVertexLayout(gl, layout);
|
|
1632
|
+
},
|
|
1633
|
+
hitTest(ctx, x, y) {
|
|
1634
|
+
const { camera, width, height } = ctx;
|
|
1635
|
+
let closest = null;
|
|
1636
|
+
let closestDist = 30;
|
|
1637
|
+
for (const d of barData) {
|
|
1638
|
+
const screen = projectToScreen(new Float32Array([d.cx, d.cy, d.cz]), camera.projViewMatrix, width, height);
|
|
1639
|
+
if (!screen || screen.z < -1 || screen.z > 1) continue;
|
|
1640
|
+
const dist = Math.sqrt((screen.x - x) ** 2 + (screen.y - y) ** 2);
|
|
1641
|
+
if (dist < closestDist) {
|
|
1642
|
+
closestDist = dist;
|
|
1643
|
+
closest = { seriesIndex: d.si, dataIndex: d.di, value: d.value, x: d.cx, y: d.cy, z: d.cz, seriesName: d.name };
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
return closest;
|
|
1647
|
+
},
|
|
1648
|
+
dispose() {
|
|
1649
|
+
vbo?.destroy();
|
|
1650
|
+
vbo = null;
|
|
1651
|
+
ibo?.destroy();
|
|
1652
|
+
ibo = null;
|
|
1653
|
+
barData = [];
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
// src/charts/surface3d/surface3d-type.ts
|
|
1659
|
+
function heightToColor(v, min, range) {
|
|
1660
|
+
const t = range > 0 ? (v - min) / range : 0.5;
|
|
1661
|
+
if (t < 0.2) {
|
|
1662
|
+
const s2 = t / 0.2;
|
|
1663
|
+
return [0.1 + s2 * 0.05, 0.15 + s2 * 0.35, 0.6 + s2 * 0.2];
|
|
1664
|
+
}
|
|
1665
|
+
if (t < 0.4) {
|
|
1666
|
+
const s2 = (t - 0.2) / 0.2;
|
|
1667
|
+
return [0.15 - s2 * 0.05, 0.5 + s2 * 0.3, 0.8 - s2 * 0.25];
|
|
1668
|
+
}
|
|
1669
|
+
if (t < 0.6) {
|
|
1670
|
+
const s2 = (t - 0.4) / 0.2;
|
|
1671
|
+
return [0.1 + s2 * 0.6, 0.8 - s2 * 0.1, 0.55 - s2 * 0.35];
|
|
1672
|
+
}
|
|
1673
|
+
if (t < 0.8) {
|
|
1674
|
+
const s2 = (t - 0.6) / 0.2;
|
|
1675
|
+
return [0.7 + s2 * 0.28, 0.7 - s2 * 0.2, 0.2 - s2 * 0.05];
|
|
1676
|
+
}
|
|
1677
|
+
const s = (t - 0.8) / 0.2;
|
|
1678
|
+
return [0.98, 0.5 - s * 0.15, 0.15 + s * 0.2];
|
|
1679
|
+
}
|
|
1680
|
+
function createSurface3DPlugin() {
|
|
1681
|
+
let vbo = null;
|
|
1682
|
+
let ibo = null;
|
|
1683
|
+
let indexCount = 0;
|
|
1684
|
+
let indexType = 0;
|
|
1685
|
+
let drawMode = 0;
|
|
1686
|
+
const modelMatrix = mat4();
|
|
1687
|
+
const normalMatrix = new Float32Array(9);
|
|
1688
|
+
let gridPoints = [];
|
|
1689
|
+
return {
|
|
1690
|
+
type: "surface3d",
|
|
1691
|
+
prepare(ctx) {
|
|
1692
|
+
const { renderer, data, options } = ctx;
|
|
1693
|
+
const gl = renderer.gl;
|
|
1694
|
+
const grid = data.grid;
|
|
1695
|
+
if (!grid || grid.length === 0) return;
|
|
1696
|
+
renderer.registerProgram(
|
|
1697
|
+
"mesh",
|
|
1698
|
+
MESH_VERT,
|
|
1699
|
+
MESH_FRAG,
|
|
1700
|
+
[...MESH_VERT_UNIFORMS, ...MESH_FRAG_UNIFORMS],
|
|
1701
|
+
MESH_VERT_ATTRIBUTES
|
|
1702
|
+
);
|
|
1703
|
+
const rows = grid.length;
|
|
1704
|
+
const cols = grid[0].length;
|
|
1705
|
+
let min = Infinity, max = -Infinity;
|
|
1706
|
+
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
|
|
1707
|
+
const v = grid[r][c];
|
|
1708
|
+
if (v < min) min = v;
|
|
1709
|
+
if (v > max) max = v;
|
|
1710
|
+
}
|
|
1711
|
+
const range = max - min;
|
|
1712
|
+
const verts = new Float32Array(rows * cols * 9);
|
|
1713
|
+
gridPoints = [];
|
|
1714
|
+
let vi = 0;
|
|
1715
|
+
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
|
|
1716
|
+
const y = grid[r][c];
|
|
1717
|
+
const x = c / (cols - 1 || 1) * 10 - 5;
|
|
1718
|
+
const z = r / (rows - 1 || 1) * 10 - 5;
|
|
1719
|
+
const yl = c > 0 ? grid[r][c - 1] : y;
|
|
1720
|
+
const yr = c < cols - 1 ? grid[r][c + 1] : y;
|
|
1721
|
+
const yd = r > 0 ? grid[r - 1][c] : y;
|
|
1722
|
+
const yu = r < rows - 1 ? grid[r + 1][c] : y;
|
|
1723
|
+
const dx = 10 / (cols - 1 || 1), dz = 10 / (rows - 1 || 1);
|
|
1724
|
+
const nx = (yl - yr) / (2 * dx), nz = (yd - yu) / (2 * dz);
|
|
1725
|
+
const len = Math.sqrt(nx * nx + 1 + nz * nz);
|
|
1726
|
+
const color = heightToColor(y, min, range);
|
|
1727
|
+
verts[vi++] = x;
|
|
1728
|
+
verts[vi++] = y;
|
|
1729
|
+
verts[vi++] = z;
|
|
1730
|
+
verts[vi++] = nx / len;
|
|
1731
|
+
verts[vi++] = 1 / len;
|
|
1732
|
+
verts[vi++] = nz / len;
|
|
1733
|
+
verts[vi++] = color[0];
|
|
1734
|
+
verts[vi++] = color[1];
|
|
1735
|
+
verts[vi++] = color[2];
|
|
1736
|
+
gridPoints.push({ x, y, z, row: r, col: c });
|
|
1737
|
+
}
|
|
1738
|
+
const isWireframe = options.wireframe === true;
|
|
1739
|
+
const indices = [];
|
|
1740
|
+
if (isWireframe) {
|
|
1741
|
+
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
|
|
1742
|
+
const idx = r * cols + c;
|
|
1743
|
+
if (c < cols - 1) indices.push(idx, idx + 1);
|
|
1744
|
+
if (r < rows - 1) indices.push(idx, idx + cols);
|
|
1745
|
+
}
|
|
1746
|
+
drawMode = gl.LINES;
|
|
1747
|
+
} else {
|
|
1748
|
+
for (let r = 0; r < rows - 1; r++) for (let c = 0; c < cols - 1; c++) {
|
|
1749
|
+
const tl = r * cols + c;
|
|
1750
|
+
indices.push(tl, tl + cols, tl + 1, tl + 1, tl + cols, tl + cols + 1);
|
|
1751
|
+
}
|
|
1752
|
+
drawMode = gl.TRIANGLES;
|
|
1753
|
+
}
|
|
1754
|
+
const maxIdx = rows * cols;
|
|
1755
|
+
const use32 = maxIdx > 65535;
|
|
1756
|
+
const idxArr = use32 ? new Uint32Array(indices) : new Uint16Array(indices);
|
|
1757
|
+
if (vbo) vbo.update(verts);
|
|
1758
|
+
else vbo = createVertexBuffer(gl, verts, gl.DYNAMIC_DRAW);
|
|
1759
|
+
if (ibo) ibo.update(idxArr);
|
|
1760
|
+
else ibo = createIndexBuffer(gl, idxArr, gl.DYNAMIC_DRAW);
|
|
1761
|
+
indexCount = indices.length;
|
|
1762
|
+
indexType = use32 ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT;
|
|
1763
|
+
mat4Identity(modelMatrix);
|
|
1764
|
+
},
|
|
1765
|
+
render(ctx) {
|
|
1766
|
+
const { renderer, camera } = ctx;
|
|
1767
|
+
const gl = renderer.gl;
|
|
1768
|
+
const prog = renderer.getProgram("mesh");
|
|
1769
|
+
prog.use();
|
|
1770
|
+
prog.setMat4("u_projView", camera.projViewMatrix);
|
|
1771
|
+
prog.setMat4("u_model", modelMatrix);
|
|
1772
|
+
mat3NormalFromMat4(normalMatrix, modelMatrix);
|
|
1773
|
+
prog.setMat3("u_normalMatrix", normalMatrix);
|
|
1774
|
+
setLightUniforms(prog, defaultLightConfig(), camera.position);
|
|
1775
|
+
prog.setFloat("u_opacity", ctx.animationProgress);
|
|
1776
|
+
if (!vbo || !ibo || indexCount === 0) return;
|
|
1777
|
+
vbo.bind();
|
|
1778
|
+
const layout = createVertexLayout([
|
|
1779
|
+
{ location: prog.attributes["a_position"], size: 3 },
|
|
1780
|
+
{ location: prog.attributes["a_normal"], size: 3 },
|
|
1781
|
+
{ location: prog.attributes["a_color"], size: 3 }
|
|
1782
|
+
]);
|
|
1783
|
+
applyVertexLayout(gl, layout);
|
|
1784
|
+
ibo.bind();
|
|
1785
|
+
gl.disable(gl.CULL_FACE);
|
|
1786
|
+
gl.drawElements(drawMode, indexCount, indexType, 0);
|
|
1787
|
+
gl.enable(gl.CULL_FACE);
|
|
1788
|
+
disableVertexLayout(gl, layout);
|
|
1789
|
+
},
|
|
1790
|
+
hitTest(ctx, x, y) {
|
|
1791
|
+
const { camera, width, height, data } = ctx;
|
|
1792
|
+
let closest = null;
|
|
1793
|
+
let closestDist = 20;
|
|
1794
|
+
for (const gp of gridPoints) {
|
|
1795
|
+
const screen = projectToScreen(new Float32Array([gp.x, gp.y, gp.z]), camera.projViewMatrix, width, height);
|
|
1796
|
+
if (!screen || screen.z < -1 || screen.z > 1) continue;
|
|
1797
|
+
const dist = Math.sqrt((screen.x - x) ** 2 + (screen.y - y) ** 2);
|
|
1798
|
+
if (dist < closestDist) {
|
|
1799
|
+
closestDist = dist;
|
|
1800
|
+
closest = { seriesIndex: 0, dataIndex: gp.row * (data.grid?.[0]?.length ?? 0) + gp.col, value: gp.y, x: gp.x, y: gp.y, z: gp.z, seriesName: `[${gp.row},${gp.col}]` };
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
return closest;
|
|
1804
|
+
},
|
|
1805
|
+
dispose() {
|
|
1806
|
+
vbo?.destroy();
|
|
1807
|
+
vbo = null;
|
|
1808
|
+
ibo?.destroy();
|
|
1809
|
+
ibo = null;
|
|
1810
|
+
gridPoints = [];
|
|
1811
|
+
}
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// src/charts/globe3d/globe3d-type.ts
|
|
1816
|
+
function latLngToXYZ(lat, lng, radius) {
|
|
1817
|
+
const phi = (90 - lat) * Math.PI / 180;
|
|
1818
|
+
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)];
|
|
1820
|
+
}
|
|
1821
|
+
function createGlobe3DPlugin() {
|
|
1822
|
+
let sphereVBO = null;
|
|
1823
|
+
let sphereIBO = null;
|
|
1824
|
+
let sphereIndexCount = 0;
|
|
1825
|
+
let pointVBO = null;
|
|
1826
|
+
let pointCount = 0;
|
|
1827
|
+
const modelMatrix = mat4();
|
|
1828
|
+
const normalMatrix = new Float32Array(9);
|
|
1829
|
+
let globePoints = [];
|
|
1830
|
+
return {
|
|
1831
|
+
type: "globe3d",
|
|
1832
|
+
prepare(ctx) {
|
|
1833
|
+
const { renderer, data, theme } = ctx;
|
|
1834
|
+
const gl = renderer.gl;
|
|
1835
|
+
const series = data.series;
|
|
1836
|
+
renderer.registerProgram(
|
|
1837
|
+
"mesh",
|
|
1838
|
+
MESH_VERT,
|
|
1839
|
+
MESH_FRAG,
|
|
1840
|
+
[...MESH_VERT_UNIFORMS, ...MESH_FRAG_UNIFORMS],
|
|
1841
|
+
MESH_VERT_ATTRIBUTES
|
|
1842
|
+
);
|
|
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 = [];
|
|
1873
|
+
for (let sIdx = 0; sIdx < series.length; sIdx++) {
|
|
1874
|
+
const s = series[sIdx];
|
|
1875
|
+
const color = hexToRGB(s.color ?? theme.colors[sIdx % theme.colors.length]);
|
|
1876
|
+
for (let di = 0; di < s.values.length; di++) {
|
|
1877
|
+
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 });
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
if (pointVBO) pointVBO.update(new Float32Array(pv));
|
|
1884
|
+
else pointVBO = createVertexBuffer(gl, new Float32Array(pv), gl.DYNAMIC_DRAW);
|
|
1885
|
+
pointCount = globePoints.length;
|
|
1886
|
+
mat4Identity(modelMatrix);
|
|
1887
|
+
},
|
|
1888
|
+
render(ctx) {
|
|
1889
|
+
const { renderer, camera } = ctx;
|
|
1890
|
+
const gl = renderer.gl;
|
|
1891
|
+
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);
|
|
1896
|
+
mat3NormalFromMat4(normalMatrix, modelMatrix);
|
|
1897
|
+
meshProg.setMat3("u_normalMatrix", normalMatrix);
|
|
1898
|
+
setLightUniforms(meshProg, defaultLightConfig(), camera.position);
|
|
1899
|
+
meshProg.setFloat("u_opacity", progress * 0.9);
|
|
1900
|
+
if (sphereVBO && sphereIBO) {
|
|
1901
|
+
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 }
|
|
1906
|
+
]);
|
|
1907
|
+
applyVertexLayout(gl, ml);
|
|
1908
|
+
sphereIBO.bind();
|
|
1909
|
+
gl.drawElements(gl.TRIANGLES, sphereIndexCount, gl.UNSIGNED_SHORT, 0);
|
|
1910
|
+
disableVertexLayout(gl, ml);
|
|
1911
|
+
}
|
|
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);
|
|
1929
|
+
}
|
|
1930
|
+
},
|
|
1931
|
+
needsLoop() {
|
|
1932
|
+
return false;
|
|
1933
|
+
},
|
|
1934
|
+
hitTest(ctx, x, y) {
|
|
1935
|
+
const { camera, width, height } = ctx;
|
|
1936
|
+
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);
|
|
1940
|
+
if (!screen || screen.z < -1 || screen.z > 1) continue;
|
|
1941
|
+
const dist = Math.sqrt((screen.x - x) ** 2 + (screen.y - y) ** 2);
|
|
1942
|
+
if (dist < closestDist) {
|
|
1943
|
+
closestDist = dist;
|
|
1944
|
+
closest = { seriesIndex: gp.si, dataIndex: gp.di, value: gp.value, x: gp.lng, y: gp.lat, seriesName: gp.name };
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
return closest;
|
|
1948
|
+
},
|
|
1949
|
+
dispose() {
|
|
1950
|
+
sphereVBO?.destroy();
|
|
1951
|
+
sphereVBO = null;
|
|
1952
|
+
sphereIBO?.destroy();
|
|
1953
|
+
sphereIBO = null;
|
|
1954
|
+
pointVBO?.destroy();
|
|
1955
|
+
pointVBO = null;
|
|
1956
|
+
globePoints = [];
|
|
1957
|
+
}
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// src/charts/map3d/map3d-type.ts
|
|
1962
|
+
function valueToColor(v, min, range) {
|
|
1963
|
+
const t = range > 0 ? (v - min) / range : 0.5;
|
|
1964
|
+
return [0.2 + t * 0.6, 0.3 + (1 - t) * 0.4, 0.8 - t * 0.5];
|
|
1965
|
+
}
|
|
1966
|
+
function createMap3DPlugin() {
|
|
1967
|
+
let vbo = null;
|
|
1968
|
+
let ibo = null;
|
|
1969
|
+
let indexCount = 0;
|
|
1970
|
+
let indexType = 0;
|
|
1971
|
+
const modelMatrix = mat4();
|
|
1972
|
+
const normalMatrix = new Float32Array(9);
|
|
1973
|
+
let regionData = [];
|
|
1974
|
+
return {
|
|
1975
|
+
type: "map3d",
|
|
1976
|
+
prepare(ctx) {
|
|
1977
|
+
const { renderer, data, options } = ctx;
|
|
1978
|
+
const gl = renderer.gl;
|
|
1979
|
+
const series = data.series;
|
|
1980
|
+
renderer.registerProgram(
|
|
1981
|
+
"mesh",
|
|
1982
|
+
MESH_VERT,
|
|
1983
|
+
MESH_FRAG,
|
|
1984
|
+
[...MESH_VERT_UNIFORMS, ...MESH_FRAG_UNIFORMS],
|
|
1985
|
+
MESH_VERT_ATTRIBUTES
|
|
1986
|
+
);
|
|
1987
|
+
const verts = [], indices = [];
|
|
1988
|
+
regionData = [];
|
|
1989
|
+
let baseVertex = 0;
|
|
1990
|
+
let min = Infinity, max = -Infinity;
|
|
1991
|
+
for (const s of series) for (const v of s.values) {
|
|
1992
|
+
if (v < min) min = v;
|
|
1993
|
+
if (v > max) max = v;
|
|
1994
|
+
}
|
|
1995
|
+
const range = max - min;
|
|
1996
|
+
const extrudeHeight = options["extrudeHeight"] ?? 1;
|
|
1997
|
+
for (let si = 0; si < series.length; si++) {
|
|
1998
|
+
const s = series[si];
|
|
1999
|
+
for (let di = 0; di < s.values.length; di++) {
|
|
2000
|
+
const value = s.values[di], height = value / (max || 1) * extrudeHeight;
|
|
2001
|
+
const color = valueToColor(value, min, range);
|
|
2002
|
+
const px = s.x?.[di] ?? di % 10 * 1.2, pz = s.z?.[di] ?? Math.floor(di / 10) * 1.2;
|
|
2003
|
+
const w = 0.9, d = 0.9, topY = height;
|
|
2004
|
+
const darker = [color[0] * 0.7, color[1] * 0.7, color[2] * 0.7];
|
|
2005
|
+
verts.push(px, topY, pz, 0, 1, 0, ...color, px + w, topY, pz, 0, 1, 0, ...color, px + w, topY, pz + d, 0, 1, 0, ...color, px, topY, pz + d, 0, 1, 0, ...color);
|
|
2006
|
+
indices.push(baseVertex, baseVertex + 1, baseVertex + 2, baseVertex, baseVertex + 2, baseVertex + 3);
|
|
2007
|
+
baseVertex += 4;
|
|
2008
|
+
const sides = [
|
|
2009
|
+
[[px, 0, pz], [px + w, 0, pz], [px + w, topY, pz], [px, topY, pz], [0, 0, -1]],
|
|
2010
|
+
[[px + w, 0, pz], [px + w, 0, pz + d], [px + w, topY, pz + d], [px + w, topY, pz], [1, 0, 0]],
|
|
2011
|
+
[[px + w, 0, pz + d], [px, 0, pz + d], [px, topY, pz + d], [px + w, topY, pz + d], [0, 0, 1]],
|
|
2012
|
+
[[px, 0, pz + d], [px, 0, pz], [px, topY, pz], [px, topY, pz + d], [-1, 0, 0]]
|
|
2013
|
+
];
|
|
2014
|
+
for (const [p0, p1, p2, p3, n] of sides) {
|
|
2015
|
+
verts.push(p0[0], p0[1], p0[2], n[0], n[1], n[2], ...darker);
|
|
2016
|
+
verts.push(p1[0], p1[1], p1[2], n[0], n[1], n[2], ...darker);
|
|
2017
|
+
verts.push(p2[0], p2[1], p2[2], n[0], n[1], n[2], ...darker);
|
|
2018
|
+
verts.push(p3[0], p3[1], p3[2], n[0], n[1], n[2], ...darker);
|
|
2019
|
+
indices.push(baseVertex, baseVertex + 1, baseVertex + 2, baseVertex, baseVertex + 2, baseVertex + 3);
|
|
2020
|
+
baseVertex += 4;
|
|
2021
|
+
}
|
|
2022
|
+
regionData.push({ name: data.categories?.[di] ?? s.name, cx: px + w * 0.5, cy: topY, cz: pz + d * 0.5, value, si, di });
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
const vertArr = new Float32Array(verts);
|
|
2026
|
+
const use32 = baseVertex > 65535;
|
|
2027
|
+
const idxArr = use32 ? new Uint32Array(indices) : new Uint16Array(indices);
|
|
2028
|
+
if (vbo) vbo.update(vertArr);
|
|
2029
|
+
else vbo = createVertexBuffer(gl, vertArr, gl.DYNAMIC_DRAW);
|
|
2030
|
+
if (ibo) ibo.update(idxArr);
|
|
2031
|
+
else ibo = createIndexBuffer(gl, idxArr, gl.DYNAMIC_DRAW);
|
|
2032
|
+
indexCount = indices.length;
|
|
2033
|
+
indexType = use32 ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT;
|
|
2034
|
+
mat4Identity(modelMatrix);
|
|
2035
|
+
},
|
|
2036
|
+
render(ctx) {
|
|
2037
|
+
const { renderer, camera } = ctx;
|
|
2038
|
+
const gl = renderer.gl;
|
|
2039
|
+
const prog = renderer.getProgram("mesh");
|
|
2040
|
+
prog.use();
|
|
2041
|
+
prog.setMat4("u_projView", camera.projViewMatrix);
|
|
2042
|
+
const animModel = mat4();
|
|
2043
|
+
mat4Identity(animModel);
|
|
2044
|
+
animModel[5] = ctx.animationProgress;
|
|
2045
|
+
prog.setMat4("u_model", animModel);
|
|
2046
|
+
mat3NormalFromMat4(normalMatrix, animModel);
|
|
2047
|
+
prog.setMat3("u_normalMatrix", normalMatrix);
|
|
2048
|
+
setLightUniforms(prog, defaultLightConfig(), camera.position);
|
|
2049
|
+
prog.setFloat("u_opacity", 1);
|
|
2050
|
+
if (!vbo || !ibo || indexCount === 0) return;
|
|
2051
|
+
vbo.bind();
|
|
2052
|
+
const layout = createVertexLayout([
|
|
2053
|
+
{ location: prog.attributes["a_position"], size: 3 },
|
|
2054
|
+
{ location: prog.attributes["a_normal"], size: 3 },
|
|
2055
|
+
{ location: prog.attributes["a_color"], size: 3 }
|
|
2056
|
+
]);
|
|
2057
|
+
applyVertexLayout(gl, layout);
|
|
2058
|
+
ibo.bind();
|
|
2059
|
+
gl.drawElements(gl.TRIANGLES, indexCount, indexType, 0);
|
|
2060
|
+
disableVertexLayout(gl, layout);
|
|
2061
|
+
},
|
|
2062
|
+
renderOverlay(ctx, ctx2d) {
|
|
2063
|
+
ctx2d.font = `${ctx.theme.fontSize}px ${ctx.theme.fontFamily}`;
|
|
2064
|
+
ctx2d.fillStyle = ctx.theme.textColor;
|
|
2065
|
+
ctx2d.textAlign = "center";
|
|
2066
|
+
for (const r of regionData) {
|
|
2067
|
+
const screen = projectToScreen(new Float32Array([r.cx, r.cy + 0.3, r.cz]), ctx.camera.projViewMatrix, ctx.width, ctx.height);
|
|
2068
|
+
if (screen && screen.z > -1 && screen.z < 1) ctx2d.fillText(r.name, screen.x, screen.y);
|
|
2069
|
+
}
|
|
2070
|
+
},
|
|
2071
|
+
hitTest(ctx, x, y) {
|
|
2072
|
+
let closest = null;
|
|
2073
|
+
let closestDist = 30;
|
|
2074
|
+
for (const r of regionData) {
|
|
2075
|
+
const screen = projectToScreen(new Float32Array([r.cx, r.cy, r.cz]), ctx.camera.projViewMatrix, ctx.width, ctx.height);
|
|
2076
|
+
if (!screen || screen.z < -1 || screen.z > 1) continue;
|
|
2077
|
+
const dist = Math.sqrt((screen.x - x) ** 2 + (screen.y - y) ** 2);
|
|
2078
|
+
if (dist < closestDist) {
|
|
2079
|
+
closestDist = dist;
|
|
2080
|
+
closest = { seriesIndex: r.si, dataIndex: r.di, value: r.value, x: r.cx, y: r.cy, z: r.cz, seriesName: r.name };
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
return closest;
|
|
2084
|
+
},
|
|
2085
|
+
dispose() {
|
|
2086
|
+
vbo?.destroy();
|
|
2087
|
+
vbo = null;
|
|
2088
|
+
ibo?.destroy();
|
|
2089
|
+
ibo = null;
|
|
2090
|
+
regionData = [];
|
|
2091
|
+
}
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// src/shaders/line.vert.ts
|
|
2096
|
+
var LINE_VERT = (
|
|
2097
|
+
/* glsl */
|
|
2098
|
+
`
|
|
2099
|
+
precision highp float;
|
|
2100
|
+
|
|
2101
|
+
${PROJECTION_UNIFORMS}
|
|
2102
|
+
|
|
2103
|
+
attribute vec3 a_position;
|
|
2104
|
+
attribute vec3 a_next;
|
|
2105
|
+
attribute vec3 a_color;
|
|
2106
|
+
attribute float a_side;
|
|
2107
|
+
|
|
2108
|
+
uniform vec2 u_resolution;
|
|
2109
|
+
uniform float u_lineWidth;
|
|
2110
|
+
|
|
2111
|
+
varying vec3 v_color;
|
|
2112
|
+
varying float v_side;
|
|
2113
|
+
|
|
2114
|
+
void main() {
|
|
2115
|
+
vec4 clipCurrent = u_projView * u_model * vec4(a_position, 1.0);
|
|
2116
|
+
vec4 clipNext = u_projView * u_model * vec4(a_next, 1.0);
|
|
2117
|
+
|
|
2118
|
+
vec2 screenCurrent = clipCurrent.xy / clipCurrent.w * u_resolution * 0.5;
|
|
2119
|
+
vec2 screenNext = clipNext.xy / clipNext.w * u_resolution * 0.5;
|
|
2120
|
+
|
|
2121
|
+
vec2 dir = screenNext - screenCurrent;
|
|
2122
|
+
float len = length(dir);
|
|
2123
|
+
dir = len > 0.001 ? dir / len : vec2(1.0, 0.0);
|
|
2124
|
+
vec2 normal = vec2(-dir.y, dir.x);
|
|
2125
|
+
|
|
2126
|
+
vec2 offset = normal * u_lineWidth * 0.5 * a_side;
|
|
2127
|
+
vec2 finalScreen = screenCurrent + offset;
|
|
2128
|
+
|
|
2129
|
+
gl_Position = vec4(finalScreen / (u_resolution * 0.5) * clipCurrent.w, clipCurrent.z, clipCurrent.w);
|
|
2130
|
+
v_color = a_color;
|
|
2131
|
+
v_side = a_side;
|
|
2132
|
+
}
|
|
2133
|
+
`
|
|
2134
|
+
);
|
|
2135
|
+
var LINE_VERT_UNIFORMS = [
|
|
2136
|
+
"u_projView",
|
|
2137
|
+
"u_model",
|
|
2138
|
+
"u_resolution",
|
|
2139
|
+
"u_lineWidth"
|
|
2140
|
+
];
|
|
2141
|
+
var LINE_VERT_ATTRIBUTES = [
|
|
2142
|
+
"a_position",
|
|
2143
|
+
"a_next",
|
|
2144
|
+
"a_color",
|
|
2145
|
+
"a_side"
|
|
2146
|
+
];
|
|
2147
|
+
|
|
2148
|
+
// src/shaders/line.frag.ts
|
|
2149
|
+
var LINE_FRAG = (
|
|
2150
|
+
/* glsl */
|
|
2151
|
+
`
|
|
2152
|
+
precision highp float;
|
|
2153
|
+
|
|
2154
|
+
uniform float u_opacity;
|
|
2155
|
+
|
|
2156
|
+
varying vec3 v_color;
|
|
2157
|
+
varying float v_side;
|
|
2158
|
+
|
|
2159
|
+
void main() {
|
|
2160
|
+
float alpha = 1.0 - smoothstep(0.9, 1.0, abs(v_side));
|
|
2161
|
+
gl_FragColor = vec4(v_color, alpha * u_opacity);
|
|
2162
|
+
}
|
|
2163
|
+
`
|
|
2164
|
+
);
|
|
2165
|
+
var LINE_FRAG_UNIFORMS = ["u_opacity"];
|
|
2166
|
+
|
|
2167
|
+
// src/charts/lines3d/lines3d-type.ts
|
|
2168
|
+
function createLines3DPlugin() {
|
|
2169
|
+
let vbo = null;
|
|
2170
|
+
let ibo = null;
|
|
2171
|
+
let indexCount = 0;
|
|
2172
|
+
let indexType = 0;
|
|
2173
|
+
const modelMatrix = mat4();
|
|
2174
|
+
let linePoints = [];
|
|
2175
|
+
return {
|
|
2176
|
+
type: "lines3d",
|
|
2177
|
+
prepare(ctx) {
|
|
2178
|
+
const { renderer, data, theme } = ctx;
|
|
2179
|
+
const gl = renderer.gl;
|
|
2180
|
+
const series = data.series;
|
|
2181
|
+
renderer.registerProgram(
|
|
2182
|
+
"line3d",
|
|
2183
|
+
LINE_VERT,
|
|
2184
|
+
LINE_FRAG,
|
|
2185
|
+
[...LINE_VERT_UNIFORMS, ...LINE_FRAG_UNIFORMS],
|
|
2186
|
+
LINE_VERT_ATTRIBUTES
|
|
2187
|
+
);
|
|
2188
|
+
const verts = [];
|
|
2189
|
+
const indices = [];
|
|
2190
|
+
linePoints = [];
|
|
2191
|
+
let baseVertex = 0;
|
|
2192
|
+
for (let si = 0; si < series.length; si++) {
|
|
2193
|
+
const s = series[si];
|
|
2194
|
+
const color = hexToRGB(s.color ?? theme.colors[si % theme.colors.length]);
|
|
2195
|
+
for (let di = 0; di < s.values.length - 1; di++) {
|
|
2196
|
+
const x0 = s.x?.[di] ?? di, y0 = s.values[di], z0 = s.z?.[di] ?? 0;
|
|
2197
|
+
const x1 = s.x?.[di + 1] ?? di + 1, y1 = s.values[di + 1], z1 = s.z?.[di + 1] ?? 0;
|
|
2198
|
+
for (const side of [-1, 1]) verts.push(x0, y0, z0, x1, y1, z1, color[0], color[1], color[2], side);
|
|
2199
|
+
for (const side of [-1, 1]) verts.push(x1, y1, z1, x1, y1, z1, color[0], color[1], color[2], side);
|
|
2200
|
+
indices.push(baseVertex, baseVertex + 2, baseVertex + 1, baseVertex + 1, baseVertex + 2, baseVertex + 3);
|
|
2201
|
+
baseVertex += 4;
|
|
2202
|
+
if (di === 0) linePoints.push({ si, di, x: x0, y: y0, z: z0, value: y0, name: s.name });
|
|
2203
|
+
linePoints.push({ si, di: di + 1, x: x1, y: y1, z: z1, value: y1, name: s.name });
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
const vertArr = new Float32Array(verts);
|
|
2207
|
+
const use32 = baseVertex > 65535;
|
|
2208
|
+
const idxArr = use32 ? new Uint32Array(indices) : new Uint16Array(indices);
|
|
2209
|
+
if (vbo) vbo.update(vertArr);
|
|
2210
|
+
else vbo = createVertexBuffer(gl, vertArr, gl.DYNAMIC_DRAW);
|
|
2211
|
+
if (ibo) ibo.update(idxArr);
|
|
2212
|
+
else ibo = createIndexBuffer(gl, idxArr, gl.DYNAMIC_DRAW);
|
|
2213
|
+
indexCount = indices.length;
|
|
2214
|
+
indexType = use32 ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT;
|
|
2215
|
+
mat4Identity(modelMatrix);
|
|
2216
|
+
},
|
|
2217
|
+
render(ctx) {
|
|
2218
|
+
const { renderer, camera } = ctx;
|
|
2219
|
+
const gl = renderer.gl;
|
|
2220
|
+
const prog = renderer.getProgram("line3d");
|
|
2221
|
+
prog.use();
|
|
2222
|
+
prog.setMat4("u_projView", camera.projViewMatrix);
|
|
2223
|
+
prog.setMat4("u_model", modelMatrix);
|
|
2224
|
+
prog.setVec2("u_resolution", ctx.width * renderer.pixelRatio, ctx.height * renderer.pixelRatio);
|
|
2225
|
+
prog.setFloat("u_lineWidth", (ctx.options.lineWidth ?? 2) * renderer.pixelRatio);
|
|
2226
|
+
prog.setFloat("u_opacity", ctx.animationProgress);
|
|
2227
|
+
if (!vbo || !ibo || indexCount === 0) return;
|
|
2228
|
+
vbo.bind();
|
|
2229
|
+
const layout = createVertexLayout([
|
|
2230
|
+
{ location: prog.attributes["a_position"], size: 3 },
|
|
2231
|
+
{ location: prog.attributes["a_next"], size: 3 },
|
|
2232
|
+
{ location: prog.attributes["a_color"], size: 3 },
|
|
2233
|
+
{ location: prog.attributes["a_side"], size: 1 }
|
|
2234
|
+
]);
|
|
2235
|
+
applyVertexLayout(gl, layout);
|
|
2236
|
+
gl.disable(gl.CULL_FACE);
|
|
2237
|
+
ibo.bind();
|
|
2238
|
+
gl.drawElements(gl.TRIANGLES, indexCount, indexType, 0);
|
|
2239
|
+
gl.enable(gl.CULL_FACE);
|
|
2240
|
+
disableVertexLayout(gl, layout);
|
|
2241
|
+
},
|
|
2242
|
+
hitTest(ctx, x, y) {
|
|
2243
|
+
const { camera, width, height } = ctx;
|
|
2244
|
+
let closest = null;
|
|
2245
|
+
let closestDist = 15;
|
|
2246
|
+
for (const d of linePoints) {
|
|
2247
|
+
const screen = projectToScreen(new Float32Array([d.x, d.y, d.z]), camera.projViewMatrix, width, height);
|
|
2248
|
+
if (!screen || screen.z < -1 || screen.z > 1) continue;
|
|
2249
|
+
const dist = Math.sqrt((screen.x - x) ** 2 + (screen.y - y) ** 2);
|
|
2250
|
+
if (dist < closestDist) {
|
|
2251
|
+
closestDist = dist;
|
|
2252
|
+
closest = { seriesIndex: d.si, dataIndex: d.di, value: d.value, x: d.x, y: d.y, z: d.z, seriesName: d.name };
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
return closest;
|
|
2256
|
+
},
|
|
2257
|
+
dispose() {
|
|
2258
|
+
vbo?.destroy();
|
|
2259
|
+
vbo = null;
|
|
2260
|
+
ibo?.destroy();
|
|
2261
|
+
ibo = null;
|
|
2262
|
+
linePoints = [];
|
|
2263
|
+
}
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
// src/charts/line3d/line3d-type.ts
|
|
2268
|
+
function createLine3DPlugin() {
|
|
2269
|
+
let vbo = null;
|
|
2270
|
+
let ibo = null;
|
|
2271
|
+
let indexCount = 0;
|
|
2272
|
+
let indexType = 0;
|
|
2273
|
+
const modelMatrix = mat4();
|
|
2274
|
+
const normalMatrix = new Float32Array(9);
|
|
2275
|
+
let tubePoints = [];
|
|
2276
|
+
return {
|
|
2277
|
+
type: "line3d",
|
|
2278
|
+
prepare(ctx) {
|
|
2279
|
+
const { renderer, data, options, theme } = ctx;
|
|
2280
|
+
const gl = renderer.gl;
|
|
2281
|
+
const series = data.series;
|
|
2282
|
+
const s = series[0];
|
|
2283
|
+
if (!s || s.values.length < 2) return;
|
|
2284
|
+
renderer.registerProgram(
|
|
2285
|
+
"mesh",
|
|
2286
|
+
MESH_VERT,
|
|
2287
|
+
MESH_FRAG,
|
|
2288
|
+
[...MESH_VERT_UNIFORMS, ...MESH_FRAG_UNIFORMS],
|
|
2289
|
+
MESH_VERT_ATTRIBUTES
|
|
2290
|
+
);
|
|
2291
|
+
const tubeRadius = options["tubeRadius"] ?? 0.15;
|
|
2292
|
+
const tubeSides = options["tubeSides"] ?? 8;
|
|
2293
|
+
const color = hexToRGB(s.color ?? theme.colors[0]);
|
|
2294
|
+
const n = s.values.length;
|
|
2295
|
+
const points = [];
|
|
2296
|
+
for (let i = 0; i < n; i++) points.push([s.x?.[i] ?? i, s.values[i], s.z?.[i] ?? 0]);
|
|
2297
|
+
const tangents = [];
|
|
2298
|
+
for (let i = 0; i < n; i++) {
|
|
2299
|
+
const prev = points[Math.max(0, i - 1)], next = points[Math.min(n - 1, i + 1)];
|
|
2300
|
+
const t = vec3(next[0] - prev[0], next[1] - prev[1], next[2] - prev[2]);
|
|
2301
|
+
vec3Normalize(t, t);
|
|
2302
|
+
tangents.push([t[0], t[1], t[2]]);
|
|
2303
|
+
}
|
|
2304
|
+
const t0 = tangents[0];
|
|
2305
|
+
const initNormal = Math.abs(t0[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
2306
|
+
const normals = [];
|
|
2307
|
+
const binormals = [];
|
|
2308
|
+
const N = vec3(0, 0, 0), B = vec3(0, 0, 0);
|
|
2309
|
+
const T = vec3(t0[0], t0[1], t0[2]);
|
|
2310
|
+
vec3Cross(B, T, vec3(initNormal[0], initNormal[1], initNormal[2]));
|
|
2311
|
+
vec3Normalize(B, B);
|
|
2312
|
+
vec3Cross(N, B, T);
|
|
2313
|
+
vec3Normalize(N, N);
|
|
2314
|
+
normals.push([N[0], N[1], N[2]]);
|
|
2315
|
+
binormals.push([B[0], B[1], B[2]]);
|
|
2316
|
+
for (let i = 1; i < n; i++) {
|
|
2317
|
+
const t = tangents[i];
|
|
2318
|
+
const T2 = vec3(t[0], t[1], t[2]);
|
|
2319
|
+
vec3Cross(B, T2, N);
|
|
2320
|
+
vec3Normalize(B, B);
|
|
2321
|
+
vec3Cross(N, B, T2);
|
|
2322
|
+
vec3Normalize(N, N);
|
|
2323
|
+
normals.push([N[0], N[1], N[2]]);
|
|
2324
|
+
binormals.push([B[0], B[1], B[2]]);
|
|
2325
|
+
}
|
|
2326
|
+
const verts = [];
|
|
2327
|
+
const indices = [];
|
|
2328
|
+
tubePoints = [];
|
|
2329
|
+
for (let i = 0; i < n; i++) {
|
|
2330
|
+
const p = points[i], nm = normals[i], bi = binormals[i];
|
|
2331
|
+
for (let j = 0; j < tubeSides; j++) {
|
|
2332
|
+
const angle = j / tubeSides * Math.PI * 2;
|
|
2333
|
+
const cos = Math.cos(angle), sin = Math.sin(angle);
|
|
2334
|
+
const nx = nm[0] * cos + bi[0] * sin, ny = nm[1] * cos + bi[1] * sin, nz = nm[2] * cos + bi[2] * sin;
|
|
2335
|
+
verts.push(p[0] + nx * tubeRadius, p[1] + ny * tubeRadius, p[2] + nz * tubeRadius, nx, ny, nz, color[0], color[1], color[2]);
|
|
2336
|
+
}
|
|
2337
|
+
tubePoints.push({ di: i, x: p[0], y: p[1], z: p[2], value: s.values[i], name: s.name });
|
|
2338
|
+
}
|
|
2339
|
+
for (let i = 0; i < n - 1; i++) for (let j = 0; j < tubeSides; j++) {
|
|
2340
|
+
const j2 = (j + 1) % tubeSides;
|
|
2341
|
+
const a = i * tubeSides + j, b = i * tubeSides + j2, c = (i + 1) * tubeSides + j, d = (i + 1) * tubeSides + j2;
|
|
2342
|
+
indices.push(a, c, b, b, c, d);
|
|
2343
|
+
}
|
|
2344
|
+
const vertArr = new Float32Array(verts);
|
|
2345
|
+
const use32 = verts.length / 9 > 65535;
|
|
2346
|
+
const idxArr = use32 ? new Uint32Array(indices) : new Uint16Array(indices);
|
|
2347
|
+
if (vbo) vbo.update(vertArr);
|
|
2348
|
+
else vbo = createVertexBuffer(gl, vertArr, gl.DYNAMIC_DRAW);
|
|
2349
|
+
if (ibo) ibo.update(idxArr);
|
|
2350
|
+
else ibo = createIndexBuffer(gl, idxArr, gl.DYNAMIC_DRAW);
|
|
2351
|
+
indexCount = indices.length;
|
|
2352
|
+
indexType = use32 ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT;
|
|
2353
|
+
mat4Identity(modelMatrix);
|
|
2354
|
+
},
|
|
2355
|
+
render(ctx) {
|
|
2356
|
+
const { renderer, camera } = ctx;
|
|
2357
|
+
const gl = renderer.gl;
|
|
2358
|
+
const prog = renderer.getProgram("mesh");
|
|
2359
|
+
prog.use();
|
|
2360
|
+
prog.setMat4("u_projView", camera.projViewMatrix);
|
|
2361
|
+
prog.setMat4("u_model", modelMatrix);
|
|
2362
|
+
mat3NormalFromMat4(normalMatrix, modelMatrix);
|
|
2363
|
+
prog.setMat3("u_normalMatrix", normalMatrix);
|
|
2364
|
+
setLightUniforms(prog, defaultLightConfig(), camera.position);
|
|
2365
|
+
prog.setFloat("u_opacity", ctx.animationProgress);
|
|
2366
|
+
if (!vbo || !ibo || indexCount === 0) return;
|
|
2367
|
+
vbo.bind();
|
|
2368
|
+
const layout = createVertexLayout([
|
|
2369
|
+
{ location: prog.attributes["a_position"], size: 3 },
|
|
2370
|
+
{ location: prog.attributes["a_normal"], size: 3 },
|
|
2371
|
+
{ location: prog.attributes["a_color"], size: 3 }
|
|
2372
|
+
]);
|
|
2373
|
+
applyVertexLayout(gl, layout);
|
|
2374
|
+
ibo.bind();
|
|
2375
|
+
gl.drawElements(gl.TRIANGLES, indexCount, indexType, 0);
|
|
2376
|
+
disableVertexLayout(gl, layout);
|
|
2377
|
+
},
|
|
2378
|
+
hitTest(ctx, x, y) {
|
|
2379
|
+
const { camera, width, height } = ctx;
|
|
2380
|
+
let closest = null;
|
|
2381
|
+
let closestDist = 15;
|
|
2382
|
+
for (const d of tubePoints) {
|
|
2383
|
+
const screen = projectToScreen(new Float32Array([d.x, d.y, d.z]), camera.projViewMatrix, width, height);
|
|
2384
|
+
if (!screen || screen.z < -1 || screen.z > 1) continue;
|
|
2385
|
+
const dist = Math.sqrt((screen.x - x) ** 2 + (screen.y - y) ** 2);
|
|
2386
|
+
if (dist < closestDist) {
|
|
2387
|
+
closestDist = dist;
|
|
2388
|
+
closest = { seriesIndex: 0, dataIndex: d.di, value: d.value, x: d.x, y: d.y, z: d.z, seriesName: d.name };
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
return closest;
|
|
2392
|
+
},
|
|
2393
|
+
dispose() {
|
|
2394
|
+
vbo?.destroy();
|
|
2395
|
+
vbo = null;
|
|
2396
|
+
ibo?.destroy();
|
|
2397
|
+
ibo = null;
|
|
2398
|
+
tubePoints = [];
|
|
2399
|
+
}
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
// src/shaders/flat.vert.ts
|
|
2404
|
+
var FLAT_VERT = (
|
|
2405
|
+
/* glsl */
|
|
2406
|
+
`
|
|
2407
|
+
precision highp float;
|
|
2408
|
+
|
|
2409
|
+
attribute vec2 a_position;
|
|
2410
|
+
attribute vec3 a_color;
|
|
2411
|
+
attribute float a_size;
|
|
2412
|
+
|
|
2413
|
+
uniform vec2 u_resolution;
|
|
2414
|
+
uniform float u_pixelRatio;
|
|
2415
|
+
|
|
2416
|
+
varying vec3 v_color;
|
|
2417
|
+
|
|
2418
|
+
void main() {
|
|
2419
|
+
// Convert pixel coords to clip space
|
|
2420
|
+
vec2 clipPos = (a_position / u_resolution) * 2.0 - 1.0;
|
|
2421
|
+
clipPos.y = -clipPos.y; // flip Y for screen coords
|
|
2422
|
+
gl_Position = vec4(clipPos, 0.0, 1.0);
|
|
2423
|
+
gl_PointSize = a_size * u_pixelRatio;
|
|
2424
|
+
v_color = a_color;
|
|
2425
|
+
}
|
|
2426
|
+
`
|
|
2427
|
+
);
|
|
2428
|
+
var FLAT_VERT_UNIFORMS = ["u_resolution", "u_pixelRatio"];
|
|
2429
|
+
var FLAT_VERT_ATTRIBUTES = ["a_position", "a_color", "a_size"];
|
|
2430
|
+
|
|
2431
|
+
// src/charts/scatter-gl/scatter-gl-type.ts
|
|
2432
|
+
var GRID_SIZE = 64;
|
|
2433
|
+
function toGridKey(gx, gy) {
|
|
2434
|
+
return gy * GRID_SIZE + gx;
|
|
2435
|
+
}
|
|
2436
|
+
function screenToGrid(sx, sy, w, h) {
|
|
2437
|
+
return [Math.floor(sx / w * GRID_SIZE) | 0, Math.floor(sy / h * GRID_SIZE) | 0];
|
|
2438
|
+
}
|
|
2439
|
+
function niceScale(min, max, maxTicks) {
|
|
2440
|
+
const range = max - min || 1;
|
|
2441
|
+
const rawStep = range / maxTicks;
|
|
2442
|
+
const mag = Math.pow(10, Math.floor(Math.log10(rawStep)));
|
|
2443
|
+
const norm = rawStep / mag;
|
|
2444
|
+
const step = norm < 1.5 ? mag : norm < 3 ? 2 * mag : norm < 7 ? 5 * mag : 10 * mag;
|
|
2445
|
+
const start = Math.ceil(min / step) * step;
|
|
2446
|
+
const ticks = [];
|
|
2447
|
+
for (let v = start; v <= max + step * 1e-3; v += step) ticks.push(v);
|
|
2448
|
+
return ticks;
|
|
2449
|
+
}
|
|
2450
|
+
function formatTick(v) {
|
|
2451
|
+
if (Math.abs(v) >= 1e6) return (v / 1e6).toFixed(1) + "M";
|
|
2452
|
+
if (Math.abs(v) >= 1e3) return (v / 1e3).toFixed(1) + "K";
|
|
2453
|
+
if (Number.isInteger(v)) return String(v);
|
|
2454
|
+
return v.toFixed(Math.abs(v) < 1 ? 2 : 1);
|
|
2455
|
+
}
|
|
2456
|
+
var MARGIN = { top: 20, right: 20, bottom: 40, left: 55 };
|
|
2457
|
+
function createScatterGLPlugin() {
|
|
2458
|
+
let vbo = null;
|
|
2459
|
+
let pointCount = 0;
|
|
2460
|
+
let gridCells = /* @__PURE__ */ new Map();
|
|
2461
|
+
let dataBounds = { minX: 0, maxX: 1, minY: 0, maxY: 1 };
|
|
2462
|
+
let seriesInfo = [];
|
|
2463
|
+
return {
|
|
2464
|
+
type: "scatter-gl",
|
|
2465
|
+
prepare(ctx) {
|
|
2466
|
+
const { renderer, data, options, theme, width, height } = ctx;
|
|
2467
|
+
const gl = renderer.gl;
|
|
2468
|
+
const series = data.series;
|
|
2469
|
+
renderer.registerProgram(
|
|
2470
|
+
"flat-point",
|
|
2471
|
+
FLAT_VERT,
|
|
2472
|
+
POINT_FRAG,
|
|
2473
|
+
[...FLAT_VERT_UNIFORMS, ...POINT_FRAG_UNIFORMS],
|
|
2474
|
+
FLAT_VERT_ATTRIBUTES
|
|
2475
|
+
);
|
|
2476
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
2477
|
+
for (const s of series) for (let i = 0; i < s.x.length; i++) {
|
|
2478
|
+
const x = s.x[i], y = s.y[i];
|
|
2479
|
+
if (x < minX) minX = x;
|
|
2480
|
+
if (x > maxX) maxX = x;
|
|
2481
|
+
if (y < minY) minY = y;
|
|
2482
|
+
if (y > maxY) maxY = y;
|
|
2483
|
+
}
|
|
2484
|
+
if (minX === Infinity) {
|
|
2485
|
+
minX = 0;
|
|
2486
|
+
maxX = 100;
|
|
2487
|
+
minY = 0;
|
|
2488
|
+
maxY = 100;
|
|
2489
|
+
}
|
|
2490
|
+
const padX = (maxX - minX) * 0.05 || 0.5;
|
|
2491
|
+
const padY = (maxY - minY) * 0.05 || 0.5;
|
|
2492
|
+
minX -= padX;
|
|
2493
|
+
maxX += padX;
|
|
2494
|
+
minY -= padY;
|
|
2495
|
+
maxY += padY;
|
|
2496
|
+
dataBounds = { minX, maxX, minY, maxY };
|
|
2497
|
+
const rangeX = maxX - minX || 1, rangeY = maxY - minY || 1;
|
|
2498
|
+
const plotLeft = MARGIN.left, plotRight = width - MARGIN.right;
|
|
2499
|
+
const plotTop = MARGIN.top, plotBottom = height - MARGIN.bottom;
|
|
2500
|
+
const plotW = plotRight - plotLeft, plotH = plotBottom - plotTop;
|
|
2501
|
+
let totalPoints = 0;
|
|
2502
|
+
for (const s of series) totalPoints += s.x.length;
|
|
2503
|
+
const verts = new Float32Array(totalPoints * 6);
|
|
2504
|
+
gridCells = /* @__PURE__ */ new Map();
|
|
2505
|
+
seriesInfo = [];
|
|
2506
|
+
let vi = 0;
|
|
2507
|
+
const defaultSize = options.pointSize ?? 4;
|
|
2508
|
+
for (let si = 0; si < series.length; si++) {
|
|
2509
|
+
const s = series[si];
|
|
2510
|
+
const colorHex = s.color ?? theme.colors[si % theme.colors.length];
|
|
2511
|
+
const color = hexToRGB(colorHex);
|
|
2512
|
+
const size = s.size ?? defaultSize;
|
|
2513
|
+
seriesInfo.push({ name: s.name, color: colorHex, count: s.x.length });
|
|
2514
|
+
for (let di = 0; di < s.x.length; di++) {
|
|
2515
|
+
const sx = plotLeft + (s.x[di] - minX) / rangeX * plotW;
|
|
2516
|
+
const sy = plotTop + (1 - (s.y[di] - minY) / rangeY) * plotH;
|
|
2517
|
+
verts[vi++] = sx;
|
|
2518
|
+
verts[vi++] = sy;
|
|
2519
|
+
verts[vi++] = color[0];
|
|
2520
|
+
verts[vi++] = color[1];
|
|
2521
|
+
verts[vi++] = color[2];
|
|
2522
|
+
verts[vi++] = size;
|
|
2523
|
+
const [gx, gy] = screenToGrid(sx, sy, width, height);
|
|
2524
|
+
if (gx >= 0 && gx < GRID_SIZE && gy >= 0 && gy < GRID_SIZE) {
|
|
2525
|
+
const key = toGridKey(gx, gy);
|
|
2526
|
+
let cell = gridCells.get(key);
|
|
2527
|
+
if (!cell) {
|
|
2528
|
+
cell = [];
|
|
2529
|
+
gridCells.set(key, cell);
|
|
2530
|
+
}
|
|
2531
|
+
cell.push({ si, di, sx, sy, value: s.y[di], name: s.name });
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
if (vbo) vbo.update(verts);
|
|
2536
|
+
else vbo = createVertexBuffer(gl, verts, gl.DYNAMIC_DRAW);
|
|
2537
|
+
pointCount = totalPoints;
|
|
2538
|
+
},
|
|
2539
|
+
render(ctx) {
|
|
2540
|
+
const { renderer } = ctx;
|
|
2541
|
+
const gl = renderer.gl;
|
|
2542
|
+
const prog = renderer.getProgram("flat-point");
|
|
2543
|
+
prog.use();
|
|
2544
|
+
prog.setVec2("u_resolution", ctx.width, ctx.height);
|
|
2545
|
+
prog.setFloat("u_pixelRatio", renderer.pixelRatio);
|
|
2546
|
+
prog.setFloat("u_opacity", ctx.animationProgress);
|
|
2547
|
+
if (!vbo || pointCount === 0) return;
|
|
2548
|
+
vbo.bind();
|
|
2549
|
+
const layout = createVertexLayout([
|
|
2550
|
+
{ location: prog.attributes["a_position"], size: 2 },
|
|
2551
|
+
{ location: prog.attributes["a_color"], size: 3 },
|
|
2552
|
+
{ location: prog.attributes["a_size"], size: 1 }
|
|
2553
|
+
]);
|
|
2554
|
+
applyVertexLayout(gl, layout);
|
|
2555
|
+
gl.disable(gl.DEPTH_TEST);
|
|
2556
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
|
|
2557
|
+
gl.drawArrays(gl.POINTS, 0, pointCount);
|
|
2558
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
2559
|
+
gl.enable(gl.DEPTH_TEST);
|
|
2560
|
+
disableVertexLayout(gl, layout);
|
|
2561
|
+
},
|
|
2562
|
+
renderOverlay(ctx, ctx2d) {
|
|
2563
|
+
const { width, height, theme } = ctx;
|
|
2564
|
+
const { minX, maxX, minY, maxY } = dataBounds;
|
|
2565
|
+
const plotLeft = MARGIN.left, plotRight = width - MARGIN.right;
|
|
2566
|
+
const plotTop = MARGIN.top, plotBottom = height - MARGIN.bottom;
|
|
2567
|
+
const plotW = plotRight - plotLeft, plotH = plotBottom - plotTop;
|
|
2568
|
+
const rangeX = maxX - minX || 1, rangeY = maxY - minY || 1;
|
|
2569
|
+
ctx2d.save();
|
|
2570
|
+
const xTicks = niceScale(minX, maxX, 6);
|
|
2571
|
+
const yTicks = niceScale(minY, maxY, 5);
|
|
2572
|
+
ctx2d.strokeStyle = theme.gridColor;
|
|
2573
|
+
ctx2d.lineWidth = 1;
|
|
2574
|
+
ctx2d.globalAlpha = 0.4;
|
|
2575
|
+
ctx2d.beginPath();
|
|
2576
|
+
for (const v of xTicks) {
|
|
2577
|
+
const sx = plotLeft + (v - minX) / rangeX * plotW;
|
|
2578
|
+
ctx2d.moveTo(sx, plotTop);
|
|
2579
|
+
ctx2d.lineTo(sx, plotBottom);
|
|
2580
|
+
}
|
|
2581
|
+
for (const v of yTicks) {
|
|
2582
|
+
const sy = plotTop + (1 - (v - minY) / rangeY) * plotH;
|
|
2583
|
+
ctx2d.moveTo(plotLeft, sy);
|
|
2584
|
+
ctx2d.lineTo(plotRight, sy);
|
|
2585
|
+
}
|
|
2586
|
+
ctx2d.stroke();
|
|
2587
|
+
ctx2d.globalAlpha = 1;
|
|
2588
|
+
ctx2d.strokeStyle = theme.textColor;
|
|
2589
|
+
ctx2d.globalAlpha = 0.5;
|
|
2590
|
+
ctx2d.lineWidth = 1;
|
|
2591
|
+
ctx2d.beginPath();
|
|
2592
|
+
ctx2d.moveTo(plotLeft, plotTop);
|
|
2593
|
+
ctx2d.lineTo(plotLeft, plotBottom);
|
|
2594
|
+
ctx2d.lineTo(plotRight, plotBottom);
|
|
2595
|
+
ctx2d.stroke();
|
|
2596
|
+
ctx2d.globalAlpha = 1;
|
|
2597
|
+
ctx2d.font = `${theme.fontSize - 1}px ${theme.fontFamily}`;
|
|
2598
|
+
ctx2d.fillStyle = theme.textColor;
|
|
2599
|
+
ctx2d.globalAlpha = 0.7;
|
|
2600
|
+
ctx2d.textAlign = "center";
|
|
2601
|
+
ctx2d.textBaseline = "top";
|
|
2602
|
+
for (const v of xTicks) {
|
|
2603
|
+
const sx = plotLeft + (v - minX) / rangeX * plotW;
|
|
2604
|
+
ctx2d.fillText(formatTick(v), sx, plotBottom + 6);
|
|
2605
|
+
}
|
|
2606
|
+
ctx2d.textAlign = "right";
|
|
2607
|
+
ctx2d.textBaseline = "middle";
|
|
2608
|
+
for (const v of yTicks) {
|
|
2609
|
+
const sy = plotTop + (1 - (v - minY) / rangeY) * plotH;
|
|
2610
|
+
ctx2d.fillText(formatTick(v), plotLeft - 8, sy);
|
|
2611
|
+
}
|
|
2612
|
+
ctx2d.globalAlpha = 1;
|
|
2613
|
+
if (seriesInfo.length > 1) {
|
|
2614
|
+
const legendX = plotRight - 10;
|
|
2615
|
+
let legendY = plotTop + 8;
|
|
2616
|
+
ctx2d.textAlign = "right";
|
|
2617
|
+
ctx2d.textBaseline = "top";
|
|
2618
|
+
ctx2d.font = `${theme.fontSize - 1}px ${theme.fontFamily}`;
|
|
2619
|
+
for (const s of seriesInfo) {
|
|
2620
|
+
ctx2d.fillStyle = s.color;
|
|
2621
|
+
ctx2d.globalAlpha = 0.9;
|
|
2622
|
+
ctx2d.beginPath();
|
|
2623
|
+
ctx2d.arc(legendX - ctx2d.measureText(s.name).width - 10, legendY + 6, 4, 0, Math.PI * 2);
|
|
2624
|
+
ctx2d.fill();
|
|
2625
|
+
ctx2d.fillStyle = theme.textColor;
|
|
2626
|
+
ctx2d.globalAlpha = 0.8;
|
|
2627
|
+
ctx2d.fillText(s.name, legendX, legendY);
|
|
2628
|
+
legendY += 18;
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
ctx2d.textAlign = "left";
|
|
2632
|
+
ctx2d.textBaseline = "top";
|
|
2633
|
+
ctx2d.font = `${theme.fontSize - 2}px ${theme.fontFamily}`;
|
|
2634
|
+
ctx2d.fillStyle = theme.textColor;
|
|
2635
|
+
ctx2d.globalAlpha = 0.4;
|
|
2636
|
+
const totalPts = seriesInfo.reduce((sum, s) => sum + s.count, 0);
|
|
2637
|
+
const label = totalPts >= 1e3 ? `${(totalPts / 1e3).toFixed(totalPts >= 1e4 ? 0 : 1)}K points` : `${totalPts} points`;
|
|
2638
|
+
ctx2d.fillText(label, plotLeft + 4, plotTop + 4);
|
|
2639
|
+
ctx2d.restore();
|
|
2640
|
+
},
|
|
2641
|
+
hitTest(ctx, x, y) {
|
|
2642
|
+
const { width, height } = ctx;
|
|
2643
|
+
const [gx, gy] = screenToGrid(x, y, width, height);
|
|
2644
|
+
let closest = null;
|
|
2645
|
+
let closestDist = 10;
|
|
2646
|
+
for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
|
|
2647
|
+
const cx = gx + dx, cy = gy + dy;
|
|
2648
|
+
if (cx < 0 || cx >= GRID_SIZE || cy < 0 || cy >= GRID_SIZE) continue;
|
|
2649
|
+
const cell = gridCells.get(toGridKey(cx, cy));
|
|
2650
|
+
if (!cell) continue;
|
|
2651
|
+
for (const p of cell) {
|
|
2652
|
+
const d = Math.sqrt((p.sx - x) ** 2 + (p.sy - y) ** 2);
|
|
2653
|
+
if (d < closestDist) {
|
|
2654
|
+
closestDist = d;
|
|
2655
|
+
closest = { seriesIndex: p.si, dataIndex: p.di, value: p.value, x: p.sx, y: p.sy, seriesName: p.name };
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
return closest;
|
|
2660
|
+
},
|
|
2661
|
+
dispose() {
|
|
2662
|
+
vbo?.destroy();
|
|
2663
|
+
vbo = null;
|
|
2664
|
+
gridCells.clear();
|
|
2665
|
+
}
|
|
2666
|
+
};
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
// src/charts/lines-gl/lines-gl-type.ts
|
|
2670
|
+
var FLAT_LINE_VERT = `precision highp float; attribute vec2 a_position; attribute vec2 a_next; attribute vec3 a_color; attribute float a_side; uniform vec2 u_resolution; uniform float u_lineWidth; varying vec3 v_color; void main() { vec2 dir = a_next - a_position; float len = length(dir); dir = len > 0.001 ? dir / len : vec2(1.0, 0.0); vec2 normal = vec2(-dir.y, dir.x); vec2 pos = a_position + normal * u_lineWidth * 0.5 * a_side; vec2 c = (pos / u_resolution) * 2.0 - 1.0; c.y = -c.y; gl_Position = vec4(c, 0.0, 1.0); v_color = a_color; }`;
|
|
2671
|
+
var FLAT_LINE_FRAG = `precision highp float; uniform float u_opacity; varying vec3 v_color; void main() { gl_FragColor = vec4(v_color, u_opacity); }`;
|
|
2672
|
+
function createLinesGLPlugin() {
|
|
2673
|
+
let vbo = null;
|
|
2674
|
+
let ibo = null;
|
|
2675
|
+
let indexCount = 0;
|
|
2676
|
+
let indexType = 0;
|
|
2677
|
+
let linePoints = [];
|
|
2678
|
+
return {
|
|
2679
|
+
type: "lines-gl",
|
|
2680
|
+
prepare(ctx) {
|
|
2681
|
+
const { renderer, data, theme, width, height } = ctx;
|
|
2682
|
+
const gl = renderer.gl;
|
|
2683
|
+
const series = data.series;
|
|
2684
|
+
renderer.registerProgram(
|
|
2685
|
+
"flat-line",
|
|
2686
|
+
FLAT_LINE_VERT,
|
|
2687
|
+
FLAT_LINE_FRAG,
|
|
2688
|
+
["u_resolution", "u_lineWidth", "u_opacity"],
|
|
2689
|
+
["a_position", "a_next", "a_color", "a_side"]
|
|
2690
|
+
);
|
|
2691
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
2692
|
+
for (const s of series) for (let i = 0; i < s.x.length; i++) {
|
|
2693
|
+
const x = s.x[i], y = s.y[i];
|
|
2694
|
+
if (x < minX) minX = x;
|
|
2695
|
+
if (x > maxX) maxX = x;
|
|
2696
|
+
if (y < minY) minY = y;
|
|
2697
|
+
if (y > maxY) maxY = y;
|
|
2698
|
+
}
|
|
2699
|
+
const rangeX = maxX - minX || 1, rangeY = maxY - minY || 1, margin = 40;
|
|
2700
|
+
const toScreen = (dx, dy) => [
|
|
2701
|
+
margin + (dx - minX) / rangeX * (width - margin * 2),
|
|
2702
|
+
margin + (1 - (dy - minY) / rangeY) * (height - margin * 2)
|
|
2703
|
+
];
|
|
2704
|
+
const verts = [], indices = [];
|
|
2705
|
+
linePoints = [];
|
|
2706
|
+
let baseVertex = 0;
|
|
2707
|
+
for (let si = 0; si < series.length; si++) {
|
|
2708
|
+
const s = series[si];
|
|
2709
|
+
const color = hexToRGB(s.color ?? theme.colors[si % theme.colors.length]);
|
|
2710
|
+
for (let di = 0; di < s.x.length; di++) {
|
|
2711
|
+
const [sx, sy] = toScreen(s.x[di], s.y[di]);
|
|
2712
|
+
linePoints.push({ si, di, sx, sy, value: s.y[di], name: s.name });
|
|
2713
|
+
}
|
|
2714
|
+
for (let di = 0; di < s.x.length - 1; di++) {
|
|
2715
|
+
const [sx0, sy0] = toScreen(s.x[di], s.y[di]);
|
|
2716
|
+
const [sx1, sy1] = toScreen(s.x[di + 1], s.y[di + 1]);
|
|
2717
|
+
for (const side of [-1, 1]) verts.push(sx0, sy0, sx1, sy1, color[0], color[1], color[2], side);
|
|
2718
|
+
for (const side of [-1, 1]) verts.push(sx1, sy1, sx1, sy1, color[0], color[1], color[2], side);
|
|
2719
|
+
indices.push(baseVertex, baseVertex + 2, baseVertex + 1, baseVertex + 1, baseVertex + 2, baseVertex + 3);
|
|
2720
|
+
baseVertex += 4;
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
const vertArr = new Float32Array(verts);
|
|
2724
|
+
const use32 = baseVertex > 65535;
|
|
2725
|
+
const idxArr = use32 ? new Uint32Array(indices) : new Uint16Array(indices);
|
|
2726
|
+
if (vbo) vbo.update(vertArr);
|
|
2727
|
+
else vbo = createVertexBuffer(gl, vertArr, gl.DYNAMIC_DRAW);
|
|
2728
|
+
if (ibo) ibo.update(idxArr);
|
|
2729
|
+
else ibo = createIndexBuffer(gl, idxArr, gl.DYNAMIC_DRAW);
|
|
2730
|
+
indexCount = indices.length;
|
|
2731
|
+
indexType = use32 ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT;
|
|
2732
|
+
},
|
|
2733
|
+
render(ctx) {
|
|
2734
|
+
const { renderer } = ctx;
|
|
2735
|
+
const gl = renderer.gl;
|
|
2736
|
+
const prog = renderer.getProgram("flat-line");
|
|
2737
|
+
prog.use();
|
|
2738
|
+
prog.setVec2("u_resolution", ctx.width, ctx.height);
|
|
2739
|
+
prog.setFloat("u_lineWidth", ctx.options.lineWidth ?? 2);
|
|
2740
|
+
prog.setFloat("u_opacity", ctx.animationProgress);
|
|
2741
|
+
if (!vbo || !ibo || indexCount === 0) return;
|
|
2742
|
+
vbo.bind();
|
|
2743
|
+
const layout = createVertexLayout([
|
|
2744
|
+
{ location: prog.attributes["a_position"], size: 2 },
|
|
2745
|
+
{ location: prog.attributes["a_next"], size: 2 },
|
|
2746
|
+
{ location: prog.attributes["a_color"], size: 3 },
|
|
2747
|
+
{ location: prog.attributes["a_side"], size: 1 }
|
|
2748
|
+
]);
|
|
2749
|
+
applyVertexLayout(gl, layout);
|
|
2750
|
+
gl.disable(gl.DEPTH_TEST);
|
|
2751
|
+
ibo.bind();
|
|
2752
|
+
gl.drawElements(gl.TRIANGLES, indexCount, indexType, 0);
|
|
2753
|
+
gl.enable(gl.DEPTH_TEST);
|
|
2754
|
+
disableVertexLayout(gl, layout);
|
|
2755
|
+
},
|
|
2756
|
+
hitTest(_ctx, x, y) {
|
|
2757
|
+
let closest = null;
|
|
2758
|
+
let closestDist = 10;
|
|
2759
|
+
for (const p of linePoints) {
|
|
2760
|
+
const d = Math.sqrt((p.sx - x) ** 2 + (p.sy - y) ** 2);
|
|
2761
|
+
if (d < closestDist) {
|
|
2762
|
+
closestDist = d;
|
|
2763
|
+
closest = { seriesIndex: p.si, dataIndex: p.di, value: p.value, x: p.sx, y: p.sy, seriesName: p.name };
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
return closest;
|
|
2767
|
+
},
|
|
2768
|
+
dispose() {
|
|
2769
|
+
vbo?.destroy();
|
|
2770
|
+
vbo = null;
|
|
2771
|
+
ibo?.destroy();
|
|
2772
|
+
ibo = null;
|
|
2773
|
+
linePoints = [];
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
// src/shaders/particle.vert.ts
|
|
2779
|
+
var PARTICLE_VERT = (
|
|
2780
|
+
/* glsl */
|
|
2781
|
+
`
|
|
2782
|
+
precision highp float;
|
|
2783
|
+
|
|
2784
|
+
attribute vec2 a_position;
|
|
2785
|
+
attribute vec2 a_velocity;
|
|
2786
|
+
attribute float a_age;
|
|
2787
|
+
attribute float a_speed;
|
|
2788
|
+
|
|
2789
|
+
uniform vec2 u_resolution;
|
|
2790
|
+
uniform float u_pointSize;
|
|
2791
|
+
uniform float u_pixelRatio;
|
|
2792
|
+
uniform float u_speedRange; // max speed for normalization
|
|
2793
|
+
uniform float u_sizeBySpeed; // 0..1 how much speed affects size
|
|
2794
|
+
|
|
2795
|
+
varying float v_age;
|
|
2796
|
+
varying float v_speed; // normalized 0..1
|
|
2797
|
+
varying vec2 v_velocity;
|
|
2798
|
+
|
|
2799
|
+
void main() {
|
|
2800
|
+
vec2 clipPos = (a_position / u_resolution) * 2.0 - 1.0;
|
|
2801
|
+
clipPos.y = -clipPos.y;
|
|
2802
|
+
gl_Position = vec4(clipPos, 0.0, 1.0);
|
|
2803
|
+
|
|
2804
|
+
float normSpeed = clamp(a_speed / max(u_speedRange, 0.001), 0.0, 1.0);
|
|
2805
|
+
v_speed = normSpeed;
|
|
2806
|
+
v_age = a_age;
|
|
2807
|
+
v_velocity = a_velocity;
|
|
2808
|
+
|
|
2809
|
+
// Size: base + speed contribution. Faster = bigger.
|
|
2810
|
+
float speedScale = mix(1.0, 1.0 + normSpeed * 2.0, u_sizeBySpeed);
|
|
2811
|
+
// Young particles slightly larger
|
|
2812
|
+
float ageScale = 1.0 - a_age * 0.3;
|
|
2813
|
+
gl_PointSize = u_pointSize * u_pixelRatio * speedScale * ageScale;
|
|
2814
|
+
}
|
|
2815
|
+
`
|
|
2816
|
+
);
|
|
2817
|
+
var PARTICLE_VERT_UNIFORMS = [
|
|
2818
|
+
"u_resolution",
|
|
2819
|
+
"u_pointSize",
|
|
2820
|
+
"u_pixelRatio",
|
|
2821
|
+
"u_speedRange",
|
|
2822
|
+
"u_sizeBySpeed"
|
|
2823
|
+
];
|
|
2824
|
+
var PARTICLE_VERT_ATTRIBUTES = [
|
|
2825
|
+
"a_position",
|
|
2826
|
+
"a_velocity",
|
|
2827
|
+
"a_age",
|
|
2828
|
+
"a_speed"
|
|
2829
|
+
];
|
|
2830
|
+
|
|
2831
|
+
// src/shaders/particle.frag.ts
|
|
2832
|
+
var PARTICLE_FRAG = (
|
|
2833
|
+
/* glsl */
|
|
2834
|
+
`
|
|
2835
|
+
precision highp float;
|
|
2836
|
+
|
|
2837
|
+
uniform vec3 u_colorSlow; // color at speed=0
|
|
2838
|
+
uniform vec3 u_colorMid; // color at speed=0.5
|
|
2839
|
+
uniform vec3 u_colorFast; // color at speed=1
|
|
2840
|
+
uniform float u_useSpeedColor; // 0=use vertex color, 1=use speed gradient
|
|
2841
|
+
|
|
2842
|
+
varying float v_age;
|
|
2843
|
+
varying float v_speed;
|
|
2844
|
+
varying vec2 v_velocity;
|
|
2845
|
+
|
|
2846
|
+
vec3 speedGradient(float t) {
|
|
2847
|
+
if (t < 0.5) {
|
|
2848
|
+
return mix(u_colorSlow, u_colorMid, t * 2.0);
|
|
2849
|
+
}
|
|
2850
|
+
return mix(u_colorMid, u_colorFast, (t - 0.5) * 2.0);
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
void main() {
|
|
2854
|
+
vec2 coord = gl_PointCoord - vec2(0.5);
|
|
2855
|
+
|
|
2856
|
+
// Direction-aware elongation: stretch along velocity
|
|
2857
|
+
float speed = length(v_velocity);
|
|
2858
|
+
if (speed > 0.01) {
|
|
2859
|
+
vec2 dir = normalize(v_velocity);
|
|
2860
|
+
// Rotate coord into velocity-aligned frame
|
|
2861
|
+
float stretch = 1.0 + v_speed * 1.5;
|
|
2862
|
+
float cx = coord.x * dir.x + coord.y * dir.y; // along velocity
|
|
2863
|
+
float cy = -coord.x * dir.y + coord.y * dir.x; // perpendicular
|
|
2864
|
+
cx /= stretch;
|
|
2865
|
+
coord = vec2(cx, cy);
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
float dist = length(coord);
|
|
2869
|
+
if (dist > 0.5) discard;
|
|
2870
|
+
|
|
2871
|
+
// Core + glow
|
|
2872
|
+
float core = 1.0 - smoothstep(0.15, 0.35, dist);
|
|
2873
|
+
float glow = (1.0 - smoothstep(0.1, 0.5, dist)) * 0.6;
|
|
2874
|
+
float alpha = max(core, glow);
|
|
2875
|
+
|
|
2876
|
+
// Age fade: quadratic for longer visible tail
|
|
2877
|
+
float ageFade = 1.0 - v_age * v_age;
|
|
2878
|
+
alpha *= ageFade;
|
|
2879
|
+
|
|
2880
|
+
// Color: speed gradient or fallback
|
|
2881
|
+
vec3 color = speedGradient(v_speed);
|
|
2882
|
+
|
|
2883
|
+
// Brighten core of young fast particles
|
|
2884
|
+
float hotspot = core * (1.0 - v_age) * v_speed;
|
|
2885
|
+
color += vec3(0.3, 0.2, 0.1) * hotspot;
|
|
2886
|
+
|
|
2887
|
+
gl_FragColor = vec4(color, alpha);
|
|
2888
|
+
}
|
|
2889
|
+
`
|
|
2890
|
+
);
|
|
2891
|
+
var PARTICLE_FRAG_UNIFORMS = [
|
|
2892
|
+
"u_colorSlow",
|
|
2893
|
+
"u_colorMid",
|
|
2894
|
+
"u_colorFast",
|
|
2895
|
+
"u_useSpeedColor"
|
|
2896
|
+
];
|
|
2897
|
+
|
|
2898
|
+
// src/charts/flow-gl/flow-gl-type.ts
|
|
2899
|
+
var MAX_PARTICLES = 2e4;
|
|
2900
|
+
var PARTICLE_FLOATS = 6;
|
|
2901
|
+
function createFieldFn(type, w, h) {
|
|
2902
|
+
switch (type) {
|
|
2903
|
+
case "wind":
|
|
2904
|
+
return (x, y) => {
|
|
2905
|
+
const nx = x / w, ny = y / h;
|
|
2906
|
+
const base = 2.5 + Math.sin(ny * Math.PI * 2) * 0.8;
|
|
2907
|
+
const gust = Math.sin(nx * 6 + ny * 3) * 0.5;
|
|
2908
|
+
const vy = Math.cos(nx * 4) * 0.6 + Math.sin(ny * 5 + nx * 2) * 0.3;
|
|
2909
|
+
return [base + gust, vy];
|
|
2910
|
+
};
|
|
2911
|
+
case "vortex":
|
|
2912
|
+
return (x, y) => {
|
|
2913
|
+
const cx = w / 2, cy = h / 2;
|
|
2914
|
+
const dx = x - cx, dy = y - cy;
|
|
2915
|
+
const dist = Math.sqrt(dx * dx + dy * dy) + 1;
|
|
2916
|
+
const maxR = Math.min(w, h) * 0.45;
|
|
2917
|
+
const falloff = Math.max(0, 1 - dist / maxR);
|
|
2918
|
+
const strength = falloff * falloff * 4;
|
|
2919
|
+
return [
|
|
2920
|
+
-dy / dist * strength - dx / dist * 0.3 * falloff,
|
|
2921
|
+
dx / dist * strength - dy / dist * 0.3 * falloff
|
|
2922
|
+
];
|
|
2923
|
+
};
|
|
2924
|
+
case "source":
|
|
2925
|
+
return (x, y) => {
|
|
2926
|
+
const pts = [
|
|
2927
|
+
[w * 0.25, h * 0.35, 1.5],
|
|
2928
|
+
[w * 0.75, h * 0.65, 1.5],
|
|
2929
|
+
[w * 0.5, h * 0.5, -2.5]
|
|
2930
|
+
];
|
|
2931
|
+
let vx = 0, vy = 0;
|
|
2932
|
+
for (const [px, py, strength] of pts) {
|
|
2933
|
+
const dx = x - px, dy = y - py;
|
|
2934
|
+
const dist = Math.sqrt(dx * dx + dy * dy) + 10;
|
|
2935
|
+
vx += dx / dist * strength * (50 / dist);
|
|
2936
|
+
vy += dy / dist * strength * (50 / dist);
|
|
2937
|
+
}
|
|
2938
|
+
return [vx, vy];
|
|
2939
|
+
};
|
|
2940
|
+
case "turbulence":
|
|
2941
|
+
return (x, y) => {
|
|
2942
|
+
const nx = x / w * 6, ny = y / h * 6;
|
|
2943
|
+
const vx = Math.sin(ny * 2.3 + nx) * 2 + Math.cos(nx * 1.7 - ny * 0.8) * 1.5 + Math.sin(nx * 3.1 + ny * 2.7) * 0.5;
|
|
2944
|
+
const vy = Math.cos(nx * 2.1 - ny * 1.3) * 2 + Math.sin(ny * 1.9 + nx * 0.6) * 1.5 - Math.cos(ny * 2.8 + nx * 1.5) * 0.5;
|
|
2945
|
+
return [vx, vy];
|
|
2946
|
+
};
|
|
2947
|
+
case "swirl":
|
|
2948
|
+
default:
|
|
2949
|
+
return (x, y) => {
|
|
2950
|
+
const nx = x / w - 0.5, ny = y / h - 0.5;
|
|
2951
|
+
const angle = Math.atan2(ny, nx);
|
|
2952
|
+
const dist = Math.sqrt(nx * nx + ny * ny);
|
|
2953
|
+
const spiral = dist * 8 + angle * 2;
|
|
2954
|
+
const strength = Math.max(0, 1 - dist * 2.5) * 3;
|
|
2955
|
+
return [
|
|
2956
|
+
Math.cos(spiral) * strength + Math.sin(ny * 6) * 0.5,
|
|
2957
|
+
Math.sin(spiral) * strength + Math.cos(nx * 6) * 0.5
|
|
2958
|
+
];
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
function createFlowGLPlugin() {
|
|
2963
|
+
let vbo = null;
|
|
2964
|
+
let positions;
|
|
2965
|
+
let velocities;
|
|
2966
|
+
let ages;
|
|
2967
|
+
let speeds;
|
|
2968
|
+
let particleSpeeds;
|
|
2969
|
+
let fieldFn = () => [1, 0];
|
|
2970
|
+
let particleCount = 0;
|
|
2971
|
+
let fieldWidth = 0;
|
|
2972
|
+
let fieldHeight = 0;
|
|
2973
|
+
let maxSpeed = 1;
|
|
2974
|
+
let arrowCache = [];
|
|
2975
|
+
let opts = {};
|
|
2976
|
+
function resetParticle(i, w, h) {
|
|
2977
|
+
positions[i * 2] = Math.random() * w;
|
|
2978
|
+
positions[i * 2 + 1] = Math.random() * h;
|
|
2979
|
+
ages[i] = Math.random() * 0.2;
|
|
2980
|
+
particleSpeeds[i] = 0.5 + Math.random() * 1.5;
|
|
2981
|
+
velocities[i * 2] = 0;
|
|
2982
|
+
velocities[i * 2 + 1] = 0;
|
|
2983
|
+
speeds[i] = 0;
|
|
2984
|
+
}
|
|
2985
|
+
function sampleArrows(w, h, density) {
|
|
2986
|
+
arrowCache = [];
|
|
2987
|
+
const stepX = w / density, stepY = h / density;
|
|
2988
|
+
let trackMaxSpeed = 0;
|
|
2989
|
+
for (let gy = 0; gy < density; gy++) {
|
|
2990
|
+
for (let gx = 0; gx < density; gx++) {
|
|
2991
|
+
const x = stepX * (gx + 0.5), y = stepY * (gy + 0.5);
|
|
2992
|
+
const [vx, vy] = fieldFn(x, y);
|
|
2993
|
+
const speed = Math.sqrt(vx * vx + vy * vy);
|
|
2994
|
+
if (speed > trackMaxSpeed) trackMaxSpeed = speed;
|
|
2995
|
+
arrowCache.push({ x, y, vx, vy, speed });
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
maxSpeed = Math.max(trackMaxSpeed, 0.01);
|
|
2999
|
+
}
|
|
3000
|
+
return {
|
|
3001
|
+
type: "flow-gl",
|
|
3002
|
+
prepare(ctx) {
|
|
3003
|
+
const { renderer, data, options, width, height } = ctx;
|
|
3004
|
+
const gl = renderer.gl;
|
|
3005
|
+
renderer.registerProgram(
|
|
3006
|
+
"particle",
|
|
3007
|
+
PARTICLE_VERT,
|
|
3008
|
+
PARTICLE_FRAG,
|
|
3009
|
+
[...PARTICLE_VERT_UNIFORMS, ...PARTICLE_FRAG_UNIFORMS],
|
|
3010
|
+
PARTICLE_VERT_ATTRIBUTES
|
|
3011
|
+
);
|
|
3012
|
+
fieldWidth = width;
|
|
3013
|
+
fieldHeight = height;
|
|
3014
|
+
opts = options;
|
|
3015
|
+
if (data.grid && data.grid.length > 0) {
|
|
3016
|
+
const rows = data.grid.length, cols = data.grid[0].length / 2;
|
|
3017
|
+
fieldFn = (x, y) => {
|
|
3018
|
+
const cx = Math.max(0, Math.min(cols - 1, Math.floor(x / width * (cols - 1))));
|
|
3019
|
+
const cy = Math.max(0, Math.min(rows - 1, Math.floor(y / height * (rows - 1))));
|
|
3020
|
+
return [data.grid[cy][cx * 2] ?? 0, data.grid[cy][cx * 2 + 1] ?? 0];
|
|
3021
|
+
};
|
|
3022
|
+
} else {
|
|
3023
|
+
const fieldType = opts.fieldType ?? "swirl";
|
|
3024
|
+
fieldFn = createFieldFn(fieldType, width, height);
|
|
3025
|
+
}
|
|
3026
|
+
particleCount = Math.min(MAX_PARTICLES, opts.particleCount ?? 5e3);
|
|
3027
|
+
positions = new Float32Array(particleCount * 2);
|
|
3028
|
+
velocities = new Float32Array(particleCount * 2);
|
|
3029
|
+
ages = new Float32Array(particleCount);
|
|
3030
|
+
speeds = new Float32Array(particleCount);
|
|
3031
|
+
particleSpeeds = new Float32Array(particleCount);
|
|
3032
|
+
for (let i = 0; i < particleCount; i++) resetParticle(i, width, height);
|
|
3033
|
+
sampleArrows(width, height, opts.arrowDensity ?? 12);
|
|
3034
|
+
const vertData = new Float32Array(particleCount * PARTICLE_FLOATS);
|
|
3035
|
+
if (vbo) vbo.update(vertData);
|
|
3036
|
+
else vbo = createVertexBuffer(gl, vertData, gl.DYNAMIC_DRAW);
|
|
3037
|
+
},
|
|
3038
|
+
render(ctx) {
|
|
3039
|
+
const { renderer } = ctx;
|
|
3040
|
+
const gl = renderer.gl;
|
|
3041
|
+
const prog = renderer.getProgram("particle");
|
|
3042
|
+
const ageSpeed = opts.ageSpeed ?? 6e-3;
|
|
3043
|
+
const colorSlow = hexToRGB(opts.colorSlow ?? "#1e3a5f");
|
|
3044
|
+
const colorMid = hexToRGB(opts.colorMid ?? "#22d3ee");
|
|
3045
|
+
const colorFast = hexToRGB(opts.colorFast ?? "#fbbf24");
|
|
3046
|
+
const vertData = new Float32Array(particleCount * PARTICLE_FLOATS);
|
|
3047
|
+
let frameMaxSpeed = 0;
|
|
3048
|
+
for (let i = 0; i < particleCount; i++) {
|
|
3049
|
+
const px = positions[i * 2], py = positions[i * 2 + 1];
|
|
3050
|
+
const [u, v] = fieldFn(px, py);
|
|
3051
|
+
const spd = Math.sqrt(u * u + v * v);
|
|
3052
|
+
if (spd > frameMaxSpeed) frameMaxSpeed = spd;
|
|
3053
|
+
velocities[i * 2] = velocities[i * 2] * 0.7 + u * 0.3;
|
|
3054
|
+
velocities[i * 2 + 1] = velocities[i * 2 + 1] * 0.7 + v * 0.3;
|
|
3055
|
+
speeds[i] = spd;
|
|
3056
|
+
const vx = velocities[i * 2], vy = velocities[i * 2 + 1];
|
|
3057
|
+
positions[i * 2] = px + vx * particleSpeeds[i];
|
|
3058
|
+
positions[i * 2 + 1] = py + vy * particleSpeeds[i];
|
|
3059
|
+
ages[i] = ages[i] + ageSpeed;
|
|
3060
|
+
if (ages[i] > 1 || positions[i * 2] < 0 || positions[i * 2] > fieldWidth || positions[i * 2 + 1] < 0 || positions[i * 2 + 1] > fieldHeight) {
|
|
3061
|
+
resetParticle(i, fieldWidth, fieldHeight);
|
|
3062
|
+
}
|
|
3063
|
+
const off = i * PARTICLE_FLOATS;
|
|
3064
|
+
vertData[off] = positions[i * 2];
|
|
3065
|
+
vertData[off + 1] = positions[i * 2 + 1];
|
|
3066
|
+
vertData[off + 2] = vx;
|
|
3067
|
+
vertData[off + 3] = vy;
|
|
3068
|
+
vertData[off + 4] = ages[i];
|
|
3069
|
+
vertData[off + 5] = speeds[i];
|
|
3070
|
+
}
|
|
3071
|
+
maxSpeed = maxSpeed * 0.95 + frameMaxSpeed * 0.05;
|
|
3072
|
+
if (!vbo) return;
|
|
3073
|
+
vbo.update(vertData);
|
|
3074
|
+
vbo.bind();
|
|
3075
|
+
prog.use();
|
|
3076
|
+
prog.setVec2("u_resolution", ctx.width, ctx.height);
|
|
3077
|
+
prog.setFloat("u_pointSize", opts.pointSize ?? 4);
|
|
3078
|
+
prog.setFloat("u_pixelRatio", renderer.pixelRatio);
|
|
3079
|
+
prog.setFloat("u_speedRange", maxSpeed);
|
|
3080
|
+
prog.setFloat("u_sizeBySpeed", opts.sizeBySpeed ?? 0.7);
|
|
3081
|
+
prog.setVec3("u_colorSlow", colorSlow[0], colorSlow[1], colorSlow[2]);
|
|
3082
|
+
prog.setVec3("u_colorMid", colorMid[0], colorMid[1], colorMid[2]);
|
|
3083
|
+
prog.setVec3("u_colorFast", colorFast[0], colorFast[1], colorFast[2]);
|
|
3084
|
+
prog.setFloat("u_useSpeedColor", 1);
|
|
3085
|
+
const layout = createVertexLayout([
|
|
3086
|
+
{ location: prog.attributes["a_position"], size: 2 },
|
|
3087
|
+
{ location: prog.attributes["a_velocity"], size: 2 },
|
|
3088
|
+
{ location: prog.attributes["a_age"], size: 1 },
|
|
3089
|
+
{ location: prog.attributes["a_speed"], size: 1 }
|
|
3090
|
+
]);
|
|
3091
|
+
applyVertexLayout(gl, layout);
|
|
3092
|
+
gl.disable(gl.DEPTH_TEST);
|
|
3093
|
+
gl.drawArrays(gl.POINTS, 0, particleCount);
|
|
3094
|
+
gl.enable(gl.DEPTH_TEST);
|
|
3095
|
+
disableVertexLayout(gl, layout);
|
|
3096
|
+
},
|
|
3097
|
+
renderOverlay(ctx, ctx2d) {
|
|
3098
|
+
const showArrows = opts.showArrows ?? true;
|
|
3099
|
+
const showLegend = opts.showLegend ?? true;
|
|
3100
|
+
const { width, height, theme } = ctx;
|
|
3101
|
+
if (showArrows && arrowCache.length > 0) {
|
|
3102
|
+
ctx2d.save();
|
|
3103
|
+
const arrowLen = Math.min(width, height) / (opts.arrowDensity ?? 12) * 0.35;
|
|
3104
|
+
for (const a of arrowCache) {
|
|
3105
|
+
const speed = a.speed;
|
|
3106
|
+
if (speed < 0.01) continue;
|
|
3107
|
+
const nx = a.vx / speed, ny = a.vy / speed;
|
|
3108
|
+
const norm = Math.min(speed / maxSpeed, 1);
|
|
3109
|
+
ctx2d.globalAlpha = 0.12 + norm * 0.18;
|
|
3110
|
+
ctx2d.strokeStyle = theme.textColor;
|
|
3111
|
+
ctx2d.lineWidth = 1;
|
|
3112
|
+
ctx2d.beginPath();
|
|
3113
|
+
const len = arrowLen * (0.3 + norm * 0.7);
|
|
3114
|
+
const ex = a.x + nx * len, ey = a.y + ny * len;
|
|
3115
|
+
ctx2d.moveTo(a.x - nx * len * 0.3, a.y - ny * len * 0.3);
|
|
3116
|
+
ctx2d.lineTo(ex, ey);
|
|
3117
|
+
const headLen = len * 0.3;
|
|
3118
|
+
const hx1 = ex - headLen * (nx * 0.8 + ny * 0.5);
|
|
3119
|
+
const hy1 = ey - headLen * (ny * 0.8 - nx * 0.5);
|
|
3120
|
+
const hx2 = ex - headLen * (nx * 0.8 - ny * 0.5);
|
|
3121
|
+
const hy2 = ey - headLen * (ny * 0.8 + nx * 0.5);
|
|
3122
|
+
ctx2d.moveTo(ex, ey);
|
|
3123
|
+
ctx2d.lineTo(hx1, hy1);
|
|
3124
|
+
ctx2d.moveTo(ex, ey);
|
|
3125
|
+
ctx2d.lineTo(hx2, hy2);
|
|
3126
|
+
ctx2d.stroke();
|
|
3127
|
+
}
|
|
3128
|
+
ctx2d.restore();
|
|
3129
|
+
}
|
|
3130
|
+
if (showLegend) {
|
|
3131
|
+
ctx2d.save();
|
|
3132
|
+
const lx = width - 140, ly = height - 50;
|
|
3133
|
+
const lw = 120, lh = 10;
|
|
3134
|
+
const colorSlow = opts.colorSlow ?? "#1e3a5f";
|
|
3135
|
+
const colorMid = opts.colorMid ?? "#22d3ee";
|
|
3136
|
+
const colorFast = opts.colorFast ?? "#fbbf24";
|
|
3137
|
+
const grad = ctx2d.createLinearGradient(lx, 0, lx + lw, 0);
|
|
3138
|
+
grad.addColorStop(0, colorSlow);
|
|
3139
|
+
grad.addColorStop(0.5, colorMid);
|
|
3140
|
+
grad.addColorStop(1, colorFast);
|
|
3141
|
+
ctx2d.fillStyle = "rgba(0,0,0,0.4)";
|
|
3142
|
+
ctx2d.beginPath();
|
|
3143
|
+
ctx2d.roundRect(lx - 10, ly - 20, lw + 20, lh + 36, 6);
|
|
3144
|
+
ctx2d.fill();
|
|
3145
|
+
ctx2d.fillStyle = grad;
|
|
3146
|
+
ctx2d.beginPath();
|
|
3147
|
+
ctx2d.roundRect(lx, ly, lw, lh, 3);
|
|
3148
|
+
ctx2d.fill();
|
|
3149
|
+
ctx2d.font = `${theme.fontSize - 2}px ${theme.fontFamily}`;
|
|
3150
|
+
ctx2d.fillStyle = "rgba(255,255,255,0.7)";
|
|
3151
|
+
ctx2d.textAlign = "left";
|
|
3152
|
+
ctx2d.fillText("Slow", lx, ly + lh + 14);
|
|
3153
|
+
ctx2d.textAlign = "center";
|
|
3154
|
+
ctx2d.fillText("Speed", lx + lw / 2, ly - 6);
|
|
3155
|
+
ctx2d.textAlign = "right";
|
|
3156
|
+
ctx2d.fillText("Fast", lx + lw, ly + lh + 14);
|
|
3157
|
+
ctx2d.restore();
|
|
3158
|
+
}
|
|
3159
|
+
},
|
|
3160
|
+
needsLoop() {
|
|
3161
|
+
return true;
|
|
3162
|
+
},
|
|
3163
|
+
hitTest() {
|
|
3164
|
+
return null;
|
|
3165
|
+
},
|
|
3166
|
+
dispose() {
|
|
3167
|
+
vbo?.destroy();
|
|
3168
|
+
vbo = null;
|
|
3169
|
+
arrowCache = [];
|
|
3170
|
+
}
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
// src/charts/graph-gl/graph-gl-type.ts
|
|
3175
|
+
function insertNode(quad, node) {
|
|
3176
|
+
if (quad.mass === 0 && !quad.body) {
|
|
3177
|
+
quad.body = node;
|
|
3178
|
+
quad.cx = node.x;
|
|
3179
|
+
quad.cy = node.y;
|
|
3180
|
+
quad.mass = 1;
|
|
3181
|
+
return;
|
|
3182
|
+
}
|
|
3183
|
+
if (quad.body) {
|
|
3184
|
+
const existing = quad.body;
|
|
3185
|
+
quad.body = null;
|
|
3186
|
+
insertIntoChild(quad, existing);
|
|
3187
|
+
}
|
|
3188
|
+
quad.cx = (quad.cx * quad.mass + node.x) / (quad.mass + 1);
|
|
3189
|
+
quad.cy = (quad.cy * quad.mass + node.y) / (quad.mass + 1);
|
|
3190
|
+
quad.mass += 1;
|
|
3191
|
+
insertIntoChild(quad, node);
|
|
3192
|
+
}
|
|
3193
|
+
function insertIntoChild(quad, node) {
|
|
3194
|
+
const midX = (quad.x0 + quad.x1) / 2, midY = (quad.y0 + quad.y1) / 2;
|
|
3195
|
+
const idx = (node.x > midX ? 1 : 0) + (node.y > midY ? 2 : 0);
|
|
3196
|
+
if (!quad.children[idx]) {
|
|
3197
|
+
quad.children[idx] = { cx: 0, cy: 0, mass: 0, x0: idx & 1 ? midX : quad.x0, y0: idx & 2 ? midY : quad.y0, x1: idx & 1 ? quad.x1 : midX, y1: idx & 2 ? quad.y1 : midY, children: [null, null, null, null], body: null };
|
|
3198
|
+
}
|
|
3199
|
+
insertNode(quad.children[idx], node);
|
|
3200
|
+
}
|
|
3201
|
+
function applyBarnesHut(quad, node, theta, repulsion) {
|
|
3202
|
+
if (quad.mass === 0) return;
|
|
3203
|
+
const dx = quad.cx - node.x, dy = quad.cy - node.y;
|
|
3204
|
+
const dist = Math.sqrt(dx * dx + dy * dy) + 0.01;
|
|
3205
|
+
if ((quad.x1 - quad.x0) / dist < theta || quad.mass === 1) {
|
|
3206
|
+
const force = -500 * quad.mass / (dist * dist);
|
|
3207
|
+
node.vx += force * dx / dist;
|
|
3208
|
+
node.vy += force * dy / dist;
|
|
3209
|
+
return;
|
|
3210
|
+
}
|
|
3211
|
+
for (const child of quad.children) if (child) applyBarnesHut(child, node, theta);
|
|
3212
|
+
}
|
|
3213
|
+
var EDGE_VERT = `precision highp float; attribute vec2 a_position; attribute vec3 a_color; uniform vec2 u_resolution; varying vec3 v_color; void main() { vec2 c = (a_position / u_resolution) * 2.0 - 1.0; c.y = -c.y; gl_Position = vec4(c, 0.0, 1.0); v_color = a_color; }`;
|
|
3214
|
+
var EDGE_FRAG = `precision highp float; uniform float u_opacity; varying vec3 v_color; void main() { gl_FragColor = vec4(v_color, u_opacity * 0.4); }`;
|
|
3215
|
+
function createGraphGLPlugin() {
|
|
3216
|
+
let nodeVBO = null;
|
|
3217
|
+
let edgeVBO = null;
|
|
3218
|
+
let nodeCount = 0;
|
|
3219
|
+
let nodes = [];
|
|
3220
|
+
let edges = [];
|
|
3221
|
+
let simulating = true;
|
|
3222
|
+
let simIterations = 0;
|
|
3223
|
+
const MAX_ITER = 300;
|
|
3224
|
+
function stepSim(w, h) {
|
|
3225
|
+
if (!simulating || simIterations >= MAX_ITER) {
|
|
3226
|
+
simulating = false;
|
|
3227
|
+
return;
|
|
3228
|
+
}
|
|
3229
|
+
const root = { cx: 0, cy: 0, mass: 0, x0: 0, y0: 0, x1: w, y1: h, children: [null, null, null, null], body: null };
|
|
3230
|
+
for (const n of nodes) insertNode(root, n);
|
|
3231
|
+
for (const n of nodes) applyBarnesHut(root, n, 0.8);
|
|
3232
|
+
for (const e of edges) {
|
|
3233
|
+
const s = nodes[e.source], t = nodes[e.target];
|
|
3234
|
+
const dx = t.x - s.x, dy = t.y - s.y, dist = Math.sqrt(dx * dx + dy * dy) + 0.01;
|
|
3235
|
+
const f = 0.01 * (dist - 100), fx = f * dx / dist, fy = f * dy / dist;
|
|
3236
|
+
s.vx += fx;
|
|
3237
|
+
s.vy += fy;
|
|
3238
|
+
t.vx -= fx;
|
|
3239
|
+
t.vy -= fy;
|
|
3240
|
+
}
|
|
3241
|
+
const cx = w / 2, cy = h / 2;
|
|
3242
|
+
for (const n of nodes) {
|
|
3243
|
+
n.vx += (cx - n.x) * 1e-3;
|
|
3244
|
+
n.vy += (cy - n.y) * 1e-3;
|
|
3245
|
+
n.vx *= 0.9;
|
|
3246
|
+
n.vy *= 0.9;
|
|
3247
|
+
n.x = Math.max(20, Math.min(w - 20, n.x + n.vx));
|
|
3248
|
+
n.y = Math.max(20, Math.min(h - 20, n.y + n.vy));
|
|
3249
|
+
}
|
|
3250
|
+
simIterations++;
|
|
3251
|
+
}
|
|
3252
|
+
return {
|
|
3253
|
+
type: "graph-gl",
|
|
3254
|
+
prepare(ctx) {
|
|
3255
|
+
const { renderer, data, options, width, height } = ctx;
|
|
3256
|
+
const gl = renderer.gl;
|
|
3257
|
+
const series = data.series;
|
|
3258
|
+
renderer.registerProgram(
|
|
3259
|
+
"flat-point",
|
|
3260
|
+
FLAT_VERT,
|
|
3261
|
+
POINT_FRAG,
|
|
3262
|
+
[...FLAT_VERT_UNIFORMS, ...POINT_FRAG_UNIFORMS],
|
|
3263
|
+
FLAT_VERT_ATTRIBUTES
|
|
3264
|
+
);
|
|
3265
|
+
renderer.registerProgram(
|
|
3266
|
+
"edge",
|
|
3267
|
+
EDGE_VERT,
|
|
3268
|
+
EDGE_FRAG,
|
|
3269
|
+
["u_resolution", "u_opacity"],
|
|
3270
|
+
["a_position", "a_color"]
|
|
3271
|
+
);
|
|
3272
|
+
nodes = [];
|
|
3273
|
+
edges = options["edges"] ?? [];
|
|
3274
|
+
const s = series[0];
|
|
3275
|
+
if (s) {
|
|
3276
|
+
for (let i = 0; i < s.x.length; i++) {
|
|
3277
|
+
nodes.push({ x: Math.random() * (width - 40) + 20, y: Math.random() * (height - 40) + 20, vx: 0, vy: 0, name: data.categories?.[i] ?? `N${i}`, value: s.y[i] ?? 1, si: 0, di: i });
|
|
3278
|
+
}
|
|
3279
|
+
if (edges.length === 0) for (let i = 0; i < nodes.length; i++) {
|
|
3280
|
+
for (let e = 0; e < Math.min(3, Math.floor(Math.random() * 4)); e++) {
|
|
3281
|
+
const t = Math.floor(Math.random() * nodes.length);
|
|
3282
|
+
if (t !== i) edges.push({ source: i, target: t });
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
simulating = true;
|
|
3287
|
+
simIterations = 0;
|
|
3288
|
+
if (nodeVBO) nodeVBO.update(new Float32Array(nodes.length * 6));
|
|
3289
|
+
else nodeVBO = createVertexBuffer(gl, new Float32Array(nodes.length * 6), gl.DYNAMIC_DRAW);
|
|
3290
|
+
if (edgeVBO) edgeVBO.update(new Float32Array(edges.length * 10));
|
|
3291
|
+
else edgeVBO = createVertexBuffer(gl, new Float32Array(edges.length * 10), gl.DYNAMIC_DRAW);
|
|
3292
|
+
nodeCount = nodes.length;
|
|
3293
|
+
},
|
|
3294
|
+
render(ctx) {
|
|
3295
|
+
const { renderer, theme } = ctx;
|
|
3296
|
+
const gl = renderer.gl;
|
|
3297
|
+
const progress = ctx.animationProgress;
|
|
3298
|
+
if (simulating) for (let i = 0; i < 5; i++) stepSim(ctx.width, ctx.height);
|
|
3299
|
+
const edgeColor = hexToRGB(theme.gridColor);
|
|
3300
|
+
const defaultSize = ctx.options.pointSize ?? 6;
|
|
3301
|
+
const ed = new Float32Array(edges.length * 10);
|
|
3302
|
+
for (let i = 0; i < edges.length; i++) {
|
|
3303
|
+
const s = nodes[edges[i].source], t = nodes[edges[i].target];
|
|
3304
|
+
const o = i * 10;
|
|
3305
|
+
ed[o] = s.x;
|
|
3306
|
+
ed[o + 1] = s.y;
|
|
3307
|
+
ed[o + 2] = edgeColor[0];
|
|
3308
|
+
ed[o + 3] = edgeColor[1];
|
|
3309
|
+
ed[o + 4] = edgeColor[2];
|
|
3310
|
+
ed[o + 5] = t.x;
|
|
3311
|
+
ed[o + 6] = t.y;
|
|
3312
|
+
ed[o + 7] = edgeColor[0];
|
|
3313
|
+
ed[o + 8] = edgeColor[1];
|
|
3314
|
+
ed[o + 9] = edgeColor[2];
|
|
3315
|
+
}
|
|
3316
|
+
if (edgeVBO) edgeVBO.update(ed);
|
|
3317
|
+
const ep = renderer.getProgram("edge");
|
|
3318
|
+
ep.use();
|
|
3319
|
+
ep.setVec2("u_resolution", ctx.width, ctx.height);
|
|
3320
|
+
ep.setFloat("u_opacity", progress);
|
|
3321
|
+
if (edgeVBO) {
|
|
3322
|
+
edgeVBO.bind();
|
|
3323
|
+
const el = createVertexLayout([{ location: ep.attributes["a_position"], size: 2 }, { location: ep.attributes["a_color"], size: 3 }]);
|
|
3324
|
+
applyVertexLayout(gl, el);
|
|
3325
|
+
gl.disable(gl.DEPTH_TEST);
|
|
3326
|
+
gl.drawArrays(gl.LINES, 0, edges.length * 2);
|
|
3327
|
+
disableVertexLayout(gl, el);
|
|
3328
|
+
}
|
|
3329
|
+
const nd = new Float32Array(nodes.length * 6);
|
|
3330
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
3331
|
+
const n = nodes[i], color = hexToRGB(theme.colors[i % theme.colors.length]);
|
|
3332
|
+
const o = i * 6;
|
|
3333
|
+
nd[o] = n.x;
|
|
3334
|
+
nd[o + 1] = n.y;
|
|
3335
|
+
nd[o + 2] = color[0];
|
|
3336
|
+
nd[o + 3] = color[1];
|
|
3337
|
+
nd[o + 4] = color[2];
|
|
3338
|
+
nd[o + 5] = defaultSize + n.value * 0.5;
|
|
3339
|
+
}
|
|
3340
|
+
if (nodeVBO) nodeVBO.update(nd);
|
|
3341
|
+
const pp = renderer.getProgram("flat-point");
|
|
3342
|
+
pp.use();
|
|
3343
|
+
pp.setVec2("u_resolution", ctx.width, ctx.height);
|
|
3344
|
+
pp.setFloat("u_pixelRatio", renderer.pixelRatio);
|
|
3345
|
+
pp.setFloat("u_opacity", progress);
|
|
3346
|
+
if (nodeVBO) {
|
|
3347
|
+
nodeVBO.bind();
|
|
3348
|
+
const nl = createVertexLayout([{ location: pp.attributes["a_position"], size: 2 }, { location: pp.attributes["a_color"], size: 3 }, { location: pp.attributes["a_size"], size: 1 }]);
|
|
3349
|
+
applyVertexLayout(gl, nl);
|
|
3350
|
+
gl.drawArrays(gl.POINTS, 0, nodeCount);
|
|
3351
|
+
gl.enable(gl.DEPTH_TEST);
|
|
3352
|
+
disableVertexLayout(gl, nl);
|
|
3353
|
+
}
|
|
3354
|
+
},
|
|
3355
|
+
needsLoop() {
|
|
3356
|
+
return simulating;
|
|
3357
|
+
},
|
|
3358
|
+
renderOverlay(ctx, ctx2d) {
|
|
3359
|
+
ctx2d.font = `${ctx.theme.fontSize - 1}px ${ctx.theme.fontFamily}`;
|
|
3360
|
+
ctx2d.fillStyle = ctx.theme.textColor;
|
|
3361
|
+
ctx2d.textAlign = "center";
|
|
3362
|
+
for (const n of nodes) ctx2d.fillText(n.name, n.x, n.y - 8);
|
|
3363
|
+
},
|
|
3364
|
+
hitTest(_ctx, x, y) {
|
|
3365
|
+
let closest = null;
|
|
3366
|
+
let closestDist = 15;
|
|
3367
|
+
for (const n of nodes) {
|
|
3368
|
+
const d = Math.sqrt((n.x - x) ** 2 + (n.y - y) ** 2);
|
|
3369
|
+
if (d < closestDist) {
|
|
3370
|
+
closestDist = d;
|
|
3371
|
+
closest = { seriesIndex: n.si, dataIndex: n.di, value: n.value, x: n.x, y: n.y, seriesName: n.name };
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
return closest;
|
|
3375
|
+
},
|
|
3376
|
+
dispose() {
|
|
3377
|
+
nodeVBO?.destroy();
|
|
3378
|
+
nodeVBO = null;
|
|
3379
|
+
edgeVBO?.destroy();
|
|
3380
|
+
edgeVBO = null;
|
|
3381
|
+
nodes = [];
|
|
3382
|
+
edges = [];
|
|
3383
|
+
}
|
|
3384
|
+
};
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
// src/api/factory.ts
|
|
3388
|
+
function Scatter3D(container, opts) {
|
|
3389
|
+
const { data, ...options } = opts;
|
|
3390
|
+
return createGLChart(container, createScatter3DPlugin(), data, options);
|
|
3391
|
+
}
|
|
3392
|
+
function Bar3D(container, opts) {
|
|
3393
|
+
const { data, ...options } = opts;
|
|
3394
|
+
return createGLChart(container, createBar3DPlugin(), data, options);
|
|
3395
|
+
}
|
|
3396
|
+
function Surface3D(container, opts) {
|
|
3397
|
+
const { data, ...options } = opts;
|
|
3398
|
+
return createGLChart(container, createSurface3DPlugin(), data, options);
|
|
3399
|
+
}
|
|
3400
|
+
function Globe3D(container, opts) {
|
|
3401
|
+
const { data, ...options } = opts;
|
|
3402
|
+
if (!options.orbit) options.orbit = { autoRotate: true, autoRotateSpeed: 0.5 };
|
|
3403
|
+
if (!options.camera) options.camera = { position: [0, 2, 9], target: [0, 0, 0] };
|
|
3404
|
+
return createGLChart(container, createGlobe3DPlugin(), data, options);
|
|
3405
|
+
}
|
|
3406
|
+
function Map3D(container, opts) {
|
|
3407
|
+
const { data, ...options } = opts;
|
|
3408
|
+
if (!options.camera) {
|
|
3409
|
+
const series = data.series;
|
|
3410
|
+
let minX = Infinity, maxX = -Infinity, minZ = Infinity, maxZ = -Infinity;
|
|
3411
|
+
for (const s of series) {
|
|
3412
|
+
if (s.x) for (const v of s.x) {
|
|
3413
|
+
if (v < minX) minX = v;
|
|
3414
|
+
if (v > maxX) maxX = v;
|
|
3415
|
+
}
|
|
3416
|
+
if (s.z) for (const v of s.z) {
|
|
3417
|
+
if (v < minZ) minZ = v;
|
|
3418
|
+
if (v > maxZ) maxZ = v;
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
if (minX === Infinity) {
|
|
3422
|
+
minX = 0;
|
|
3423
|
+
maxX = 10;
|
|
3424
|
+
}
|
|
3425
|
+
if (minZ === Infinity) {
|
|
3426
|
+
minZ = 0;
|
|
3427
|
+
maxZ = 10;
|
|
3428
|
+
}
|
|
3429
|
+
const cx = (minX + maxX) / 2, cz = (minZ + maxZ) / 2;
|
|
3430
|
+
const ext = Math.max(maxX - minX, maxZ - minZ, 1);
|
|
3431
|
+
const dist = Math.max(ext * 1.8, 5);
|
|
3432
|
+
options.camera = {
|
|
3433
|
+
position: [cx + dist * 0.6, dist * 0.5, cz + dist * 0.6],
|
|
3434
|
+
target: [cx, 0, cz]
|
|
3435
|
+
};
|
|
3436
|
+
}
|
|
3437
|
+
return createGLChart(container, createMap3DPlugin(), data, options);
|
|
3438
|
+
}
|
|
3439
|
+
function Lines3D(container, opts) {
|
|
3440
|
+
const { data, ...options } = opts;
|
|
3441
|
+
return createGLChart(container, createLines3DPlugin(), data, options);
|
|
3442
|
+
}
|
|
3443
|
+
function Line3D(container, opts) {
|
|
3444
|
+
const { data, ...options } = opts;
|
|
3445
|
+
return createGLChart(container, createLine3DPlugin(), data, options);
|
|
3446
|
+
}
|
|
3447
|
+
function ScatterGL(container, opts) {
|
|
3448
|
+
const { data, ...options } = opts;
|
|
3449
|
+
return createGLChart(container, createScatterGLPlugin(), data, options);
|
|
3450
|
+
}
|
|
3451
|
+
function LinesGL(container, opts) {
|
|
3452
|
+
const { data, ...options } = opts;
|
|
3453
|
+
return createGLChart(container, createLinesGLPlugin(), data, options);
|
|
3454
|
+
}
|
|
3455
|
+
function FlowGL(container, opts) {
|
|
3456
|
+
const { data, ...options } = opts;
|
|
3457
|
+
return createGLChart(container, createFlowGLPlugin(), data, options);
|
|
3458
|
+
}
|
|
3459
|
+
function GraphGL(container, opts) {
|
|
3460
|
+
const { data, ...options } = opts;
|
|
3461
|
+
return createGLChart(container, createGraphGLPlugin(), data, options);
|
|
3462
|
+
}
|
|
3463
|
+
|
|
3464
|
+
exports.Bar3D = Bar3D;
|
|
3465
|
+
exports.DEFAULT_GL_THEME = DEFAULT_GL_THEME;
|
|
3466
|
+
exports.FlowGL = FlowGL;
|
|
3467
|
+
exports.Globe3D = Globe3D;
|
|
3468
|
+
exports.GraphGL = GraphGL;
|
|
3469
|
+
exports.LIGHT_GL_THEME = LIGHT_GL_THEME;
|
|
3470
|
+
exports.Line3D = Line3D;
|
|
3471
|
+
exports.Lines3D = Lines3D;
|
|
3472
|
+
exports.LinesGL = LinesGL;
|
|
3473
|
+
exports.Map3D = Map3D;
|
|
3474
|
+
exports.Scatter3D = Scatter3D;
|
|
3475
|
+
exports.ScatterGL = ScatterGL;
|
|
3476
|
+
exports.Surface3D = Surface3D;
|
|
3477
|
+
exports.applyVertexLayout = applyVertexLayout;
|
|
3478
|
+
exports.compileShader = compileShader;
|
|
3479
|
+
exports.createBar3DPlugin = createBar3DPlugin;
|
|
3480
|
+
exports.createCamera = createCamera;
|
|
3481
|
+
exports.createFlowGLPlugin = createFlowGLPlugin;
|
|
3482
|
+
exports.createGLChart = createGLChart;
|
|
3483
|
+
exports.createGLRenderer = createGLRenderer;
|
|
3484
|
+
exports.createGlobe3DPlugin = createGlobe3DPlugin;
|
|
3485
|
+
exports.createGraphGLPlugin = createGraphGLPlugin;
|
|
3486
|
+
exports.createGrid3D = createGrid3D;
|
|
3487
|
+
exports.createIndexBuffer = createIndexBuffer;
|
|
3488
|
+
exports.createLine3DPlugin = createLine3DPlugin;
|
|
3489
|
+
exports.createLines3DPlugin = createLines3DPlugin;
|
|
3490
|
+
exports.createLinesGLPlugin = createLinesGLPlugin;
|
|
3491
|
+
exports.createMap3DPlugin = createMap3DPlugin;
|
|
3492
|
+
exports.createOrbitControls = createOrbitControls;
|
|
3493
|
+
exports.createPickingSystem = createPickingSystem;
|
|
3494
|
+
exports.createScatter3DPlugin = createScatter3DPlugin;
|
|
3495
|
+
exports.createScatterGLPlugin = createScatterGLPlugin;
|
|
3496
|
+
exports.createShaderProgram = createShaderProgram;
|
|
3497
|
+
exports.createSurface3DPlugin = createSurface3DPlugin;
|
|
3498
|
+
exports.createVertexBuffer = createVertexBuffer;
|
|
3499
|
+
exports.createVertexLayout = createVertexLayout;
|
|
3500
|
+
exports.defaultLightConfig = defaultLightConfig;
|
|
3501
|
+
exports.hexToRGB = hexToRGB;
|
|
3502
|
+
exports.mat4 = mat4;
|
|
3503
|
+
exports.mat4Identity = mat4Identity;
|
|
3504
|
+
exports.mat4Invert = mat4Invert;
|
|
3505
|
+
exports.mat4LookAt = mat4LookAt;
|
|
3506
|
+
exports.mat4Multiply = mat4Multiply;
|
|
3507
|
+
exports.mat4Perspective = mat4Perspective;
|
|
3508
|
+
exports.projectToScreen = projectToScreen;
|
|
3509
|
+
exports.resolveTheme = resolveTheme;
|
|
3510
|
+
exports.setLightUniforms = setLightUniforms;
|
|
3511
|
+
exports.toRad = toRad;
|
|
3512
|
+
exports.updateCamera = updateCamera;
|
|
3513
|
+
exports.updateOrbitControls = updateOrbitControls;
|
|
3514
|
+
exports.vec3 = vec3;
|
|
3515
|
+
//# sourceMappingURL=chunk-Q4JAQOV3.cjs.map
|
|
3516
|
+
//# sourceMappingURL=chunk-Q4JAQOV3.cjs.map
|