aliencharts 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -59
- package/dist/aliencharts.css +1 -1
- package/dist/aliencharts.global.js +3327 -0
- package/dist/aliencharts.global.js.map +7 -0
- package/dist/aliencharts.global.min.js +159 -0
- package/dist/aliencharts.global.min.js.map +7 -0
- package/dist/index.js +26 -3
- package/dist/index.js.map +2 -2
- package/dist/react.js +1717 -889
- package/dist/react.js.map +4 -4
- package/dist/vanilla.js +1717 -889
- package/dist/vanilla.js.map +4 -4
- package/index.d.ts +39 -1
- package/package.json +3 -1
package/dist/react.js
CHANGED
|
@@ -172,6 +172,524 @@ var removeDrawingById = (drawings, drawingId) => (Array.isArray(drawings) ? draw
|
|
|
172
172
|
(drawing) => drawing?.id !== drawingId
|
|
173
173
|
);
|
|
174
174
|
|
|
175
|
+
// src/core/chartModel.js
|
|
176
|
+
var LINE_CAPABILITIES = Object.freeze({
|
|
177
|
+
appendAnimation: true,
|
|
178
|
+
drawings: true,
|
|
179
|
+
latestValue: true,
|
|
180
|
+
markers: true,
|
|
181
|
+
movingAverage: true
|
|
182
|
+
});
|
|
183
|
+
var BAR_CAPABILITIES = Object.freeze({
|
|
184
|
+
appendAnimation: false,
|
|
185
|
+
drawings: false,
|
|
186
|
+
latestValue: false,
|
|
187
|
+
markers: false,
|
|
188
|
+
movingAverage: false
|
|
189
|
+
});
|
|
190
|
+
var CHART_DEFINITIONS = Object.freeze({
|
|
191
|
+
line: Object.freeze({
|
|
192
|
+
capabilities: LINE_CAPABILITIES,
|
|
193
|
+
defaultOrientation: "vertical",
|
|
194
|
+
orientationFromSeries: false,
|
|
195
|
+
orientations: Object.freeze(["vertical"]),
|
|
196
|
+
rangeIncludesZero: false
|
|
197
|
+
}),
|
|
198
|
+
bar: Object.freeze({
|
|
199
|
+
capabilities: BAR_CAPABILITIES,
|
|
200
|
+
defaultOrientation: "vertical",
|
|
201
|
+
orientationFromSeries: true,
|
|
202
|
+
orientations: Object.freeze(["vertical", "horizontal"]),
|
|
203
|
+
rangeIncludesZero: true
|
|
204
|
+
})
|
|
205
|
+
});
|
|
206
|
+
var getChartCategories = (chart) => {
|
|
207
|
+
if (chart?.categories == null) return [];
|
|
208
|
+
if (!Array.isArray(chart.categories)) {
|
|
209
|
+
throw new TypeError(`Chart "${chart.id}" categories must be an array`);
|
|
210
|
+
}
|
|
211
|
+
const values = /* @__PURE__ */ new Set();
|
|
212
|
+
return chart.categories.map((category, index) => {
|
|
213
|
+
const normalized = typeof category === "string" ? { value: index, label: category } : { value: Number(category?.value), label: category?.label };
|
|
214
|
+
if (!Number.isFinite(normalized.value) || typeof normalized.label !== "string") {
|
|
215
|
+
throw new TypeError(
|
|
216
|
+
`Chart "${chart.id}" category ${index} must be a string or a { value, label } object`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
if (values.has(normalized.value)) {
|
|
220
|
+
throw new TypeError(
|
|
221
|
+
`Chart "${chart.id}" has duplicate category value ${normalized.value}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
values.add(normalized.value);
|
|
225
|
+
return normalized;
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
var getCategoryLabel = (chart, value) => getChartCategories(chart).find((category) => category.value === value)?.label;
|
|
229
|
+
var getSeriesType = (series) => {
|
|
230
|
+
if (series?.type == null) return "line";
|
|
231
|
+
if (Object.prototype.hasOwnProperty.call(CHART_DEFINITIONS, series.type)) {
|
|
232
|
+
return series.type;
|
|
233
|
+
}
|
|
234
|
+
throw new TypeError(`Unknown AlienCharts series type: "${series.type}"`);
|
|
235
|
+
};
|
|
236
|
+
var resolveChartDescriptor = (chart) => {
|
|
237
|
+
const series = Array.isArray(chart?.series) ? chart.series : [];
|
|
238
|
+
const categories = getChartCategories(chart);
|
|
239
|
+
const type = series.length ? getSeriesType(series[0]) : "line";
|
|
240
|
+
const definition = CHART_DEFINITIONS[type];
|
|
241
|
+
const orientation = definition.orientationFromSeries ? series[0]?.orientation || definition.defaultOrientation : definition.defaultOrientation;
|
|
242
|
+
series.forEach((item) => {
|
|
243
|
+
const itemType = getSeriesType(item);
|
|
244
|
+
if (itemType !== type) {
|
|
245
|
+
const mixedTypes = /* @__PURE__ */ new Set([type, itemType]);
|
|
246
|
+
const typeLabel = mixedTypes.has("line") && mixedTypes.has("bar") ? "line and bar" : `${type} and ${itemType}`;
|
|
247
|
+
throw new TypeError(
|
|
248
|
+
`Chart "${chart.id}" cannot mix ${typeLabel} series`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
const itemOrientation = definition.orientationFromSeries ? item.orientation || definition.defaultOrientation : definition.defaultOrientation;
|
|
252
|
+
if (!definition.orientations.includes(itemOrientation)) {
|
|
253
|
+
throw new TypeError(
|
|
254
|
+
`Chart "${chart.id}" has an invalid ${itemType} orientation`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
if (itemOrientation !== orientation) {
|
|
258
|
+
throw new TypeError(
|
|
259
|
+
`Chart "${chart.id}" cannot mix ${itemType} orientations`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
return Object.freeze({
|
|
264
|
+
capabilities: definition.capabilities,
|
|
265
|
+
categorical: categories.length > 0,
|
|
266
|
+
orientation,
|
|
267
|
+
rangeIncludesZero: definition.rangeIncludesZero,
|
|
268
|
+
rendererType: type,
|
|
269
|
+
type
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
var resolveChartDescriptors = (charts) => {
|
|
273
|
+
const descriptors = /* @__PURE__ */ new Map();
|
|
274
|
+
charts.forEach((chart) => {
|
|
275
|
+
descriptors.set(chart.id, resolveChartDescriptor(chart));
|
|
276
|
+
});
|
|
277
|
+
return descriptors;
|
|
278
|
+
};
|
|
279
|
+
var getChartDescriptor = (chart, descriptors) => descriptors?.get(chart?.id) || resolveChartDescriptor(chart);
|
|
280
|
+
var getOrderedSeries = (chart, seriesOrderByChart) => {
|
|
281
|
+
const series = Array.isArray(chart?.series) ? chart.series : [];
|
|
282
|
+
const order = seriesOrderByChart?.[chart?.id];
|
|
283
|
+
if (!Array.isArray(order) || order.length !== series.length) {
|
|
284
|
+
return series;
|
|
285
|
+
}
|
|
286
|
+
const used = /* @__PURE__ */ new Set();
|
|
287
|
+
const ordered = [];
|
|
288
|
+
order.forEach((seriesIndex) => {
|
|
289
|
+
if (!Number.isInteger(seriesIndex)) return;
|
|
290
|
+
if (seriesIndex < 0 || seriesIndex >= series.length) return;
|
|
291
|
+
if (used.has(seriesIndex)) return;
|
|
292
|
+
used.add(seriesIndex);
|
|
293
|
+
ordered.push(series[seriesIndex]);
|
|
294
|
+
});
|
|
295
|
+
return ordered.length === series.length ? ordered : series;
|
|
296
|
+
};
|
|
297
|
+
var CHART_TYPES = Object.freeze(Object.keys(CHART_DEFINITIONS));
|
|
298
|
+
|
|
299
|
+
// src/core/coordinateTransform.js
|
|
300
|
+
var safeSpan = (min, max) => Math.max(1e-12, max - min);
|
|
301
|
+
var createCoordinateTransform = ({
|
|
302
|
+
orientation = "vertical",
|
|
303
|
+
categoryRange,
|
|
304
|
+
valueRange,
|
|
305
|
+
plot
|
|
306
|
+
}) => {
|
|
307
|
+
const horizontal = orientation === "horizontal";
|
|
308
|
+
const categorySpan = safeSpan(categoryRange.min, categoryRange.max);
|
|
309
|
+
const valueSpan = safeSpan(valueRange.min, valueRange.max);
|
|
310
|
+
const categoryPixelLength = horizontal ? plot.height : plot.width;
|
|
311
|
+
const valuePixelLength = horizontal ? plot.width : plot.height;
|
|
312
|
+
const categoryRatio = (point) => horizontal ? (point.y - plot.y) / plot.height : (point.x - plot.x) / plot.width;
|
|
313
|
+
const valueRatio = (point) => horizontal ? (point.x - plot.x) / plot.width : 1 - (point.y - plot.y) / plot.height;
|
|
314
|
+
const dataToScreen = (point) => {
|
|
315
|
+
const category = point.category ?? point.x;
|
|
316
|
+
const value = point.value ?? point.y;
|
|
317
|
+
const categoryPosition = (category - categoryRange.min) / categorySpan;
|
|
318
|
+
const valuePosition = (value - valueRange.min) / valueSpan;
|
|
319
|
+
return horizontal ? {
|
|
320
|
+
x: plot.x + valuePosition * plot.width,
|
|
321
|
+
y: plot.y + categoryPosition * plot.height
|
|
322
|
+
} : {
|
|
323
|
+
x: plot.x + categoryPosition * plot.width,
|
|
324
|
+
y: plot.y + (1 - valuePosition) * plot.height
|
|
325
|
+
};
|
|
326
|
+
};
|
|
327
|
+
const screenToData = (point) => ({
|
|
328
|
+
x: categoryRange.min + categoryRatio(point) * categorySpan,
|
|
329
|
+
y: valueRange.min + valueRatio(point) * valueSpan
|
|
330
|
+
});
|
|
331
|
+
const categoryDragDelta = (start, current) => (horizontal ? current.y - start.y : current.x - start.x) / categoryPixelLength * categorySpan;
|
|
332
|
+
const valueOffsetDelta = (start, current) => horizontal ? -((current.x - start.x) / valuePixelLength) * valueSpan : (current.y - start.y) / valuePixelLength * valueSpan;
|
|
333
|
+
const categoryScaleDelta = (start, current) => horizontal ? current.y - start.y : current.x - start.x;
|
|
334
|
+
const valueScaleDelta = (start, current) => horizontal ? current.x - start.x : current.y - start.y;
|
|
335
|
+
const dataBoundsForRect = (rect) => {
|
|
336
|
+
const first = screenToData({ x: rect.left, y: rect.top });
|
|
337
|
+
const second = screenToData({
|
|
338
|
+
x: rect.left + rect.width,
|
|
339
|
+
y: rect.top + rect.height
|
|
340
|
+
});
|
|
341
|
+
return {
|
|
342
|
+
categoryMin: Math.min(first.x, second.x),
|
|
343
|
+
categoryMax: Math.max(first.x, second.x),
|
|
344
|
+
valueMin: Math.min(first.y, second.y),
|
|
345
|
+
valueMax: Math.max(first.y, second.y)
|
|
346
|
+
};
|
|
347
|
+
};
|
|
348
|
+
return Object.freeze({
|
|
349
|
+
categoryDragDelta,
|
|
350
|
+
categoryPixelLength,
|
|
351
|
+
categoryRatio,
|
|
352
|
+
categoryScaleDelta,
|
|
353
|
+
dataBoundsForRect,
|
|
354
|
+
dataToScreen,
|
|
355
|
+
horizontal,
|
|
356
|
+
orientation,
|
|
357
|
+
screenToData,
|
|
358
|
+
valueOffsetDelta,
|
|
359
|
+
valuePixelLength,
|
|
360
|
+
valueRatio,
|
|
361
|
+
valueScaleDelta
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// src/core/renderers/webglUtils.js
|
|
366
|
+
var INITIAL_GPU_VERTEX_CAPACITY = 4096;
|
|
367
|
+
var createProgram = (gl, vertexSource, fragmentSource) => {
|
|
368
|
+
const compile = (type, source) => {
|
|
369
|
+
const shader = gl.createShader(type);
|
|
370
|
+
gl.shaderSource(shader, source);
|
|
371
|
+
gl.compileShader(shader);
|
|
372
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
373
|
+
throw new Error(gl.getShaderInfoLog(shader) || "Shader compile failed");
|
|
374
|
+
}
|
|
375
|
+
return shader;
|
|
376
|
+
};
|
|
377
|
+
const program = gl.createProgram();
|
|
378
|
+
const vertexShader = compile(gl.VERTEX_SHADER, vertexSource);
|
|
379
|
+
const fragmentShader = compile(gl.FRAGMENT_SHADER, fragmentSource);
|
|
380
|
+
gl.attachShader(program, vertexShader);
|
|
381
|
+
gl.attachShader(program, fragmentShader);
|
|
382
|
+
gl.linkProgram(program);
|
|
383
|
+
gl.deleteShader(vertexShader);
|
|
384
|
+
gl.deleteShader(fragmentShader);
|
|
385
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
386
|
+
throw new Error(gl.getProgramInfoLog(program) || "Program link failed");
|
|
387
|
+
}
|
|
388
|
+
return program;
|
|
389
|
+
};
|
|
390
|
+
var hexToRgb = (color) => {
|
|
391
|
+
const value = String(color || "#38bdf8").replace("#", "");
|
|
392
|
+
const normalized = value.length === 3 ? value.split("").map((char) => char + char).join("") : value.padEnd(6, "0").slice(0, 6);
|
|
393
|
+
const number = Number.parseInt(normalized, 16);
|
|
394
|
+
return [
|
|
395
|
+
(number >> 16 & 255) / 255,
|
|
396
|
+
(number >> 8 & 255) / 255,
|
|
397
|
+
(number & 255) / 255
|
|
398
|
+
];
|
|
399
|
+
};
|
|
400
|
+
var getNextVertexCapacity = (currentCapacity, requiredCapacity) => {
|
|
401
|
+
let nextCapacity = Math.max(
|
|
402
|
+
INITIAL_GPU_VERTEX_CAPACITY,
|
|
403
|
+
currentCapacity || 0
|
|
404
|
+
);
|
|
405
|
+
while (nextCapacity < requiredCapacity) nextCapacity *= 2;
|
|
406
|
+
return nextCapacity;
|
|
407
|
+
};
|
|
408
|
+
var getDynamicBuffer = (gl, cache, key, requiredFloats) => {
|
|
409
|
+
let entry = cache.get(key);
|
|
410
|
+
if (!entry) {
|
|
411
|
+
entry = {
|
|
412
|
+
buffer: gl.createBuffer(),
|
|
413
|
+
gpuCapacity: 0,
|
|
414
|
+
vertices: new Float32Array(
|
|
415
|
+
getNextVertexCapacity(0, requiredFloats)
|
|
416
|
+
)
|
|
417
|
+
};
|
|
418
|
+
cache.set(key, entry);
|
|
419
|
+
}
|
|
420
|
+
if (entry.vertices.length < requiredFloats) {
|
|
421
|
+
entry.vertices = new Float32Array(
|
|
422
|
+
getNextVertexCapacity(entry.vertices.length, requiredFloats)
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
if (entry.gpuCapacity < entry.vertices.length) {
|
|
426
|
+
entry.gpuCapacity = entry.vertices.length;
|
|
427
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, entry.buffer);
|
|
428
|
+
gl.bufferData(
|
|
429
|
+
gl.ARRAY_BUFFER,
|
|
430
|
+
entry.gpuCapacity * Float32Array.BYTES_PER_ELEMENT,
|
|
431
|
+
gl.DYNAMIC_DRAW
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
return entry;
|
|
435
|
+
};
|
|
436
|
+
var deleteBufferCache = (gl, cache) => {
|
|
437
|
+
cache.forEach((entry) => {
|
|
438
|
+
if (entry.buffer) gl.deleteBuffer(entry.buffer);
|
|
439
|
+
});
|
|
440
|
+
cache.clear();
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// src/core/renderers/barRenderer.js
|
|
444
|
+
var BAR_GROUP_WIDTH_RATIO = 0.8;
|
|
445
|
+
var BAR_SLICE_WIDTH_RATIO = 0.9;
|
|
446
|
+
var BAR_VERTEX_FLOAT_STRIDE = 3;
|
|
447
|
+
var VERTEX_SOURCE = `#version 300 es
|
|
448
|
+
in vec2 a_unit;
|
|
449
|
+
in vec3 a_bar;
|
|
450
|
+
uniform vec2 u_resolution;
|
|
451
|
+
uniform vec4 u_rect;
|
|
452
|
+
uniform vec2 u_categoryRange;
|
|
453
|
+
uniform vec2 u_valueRange;
|
|
454
|
+
uniform float u_baseline;
|
|
455
|
+
uniform float u_seriesIndex;
|
|
456
|
+
uniform float u_seriesCount;
|
|
457
|
+
uniform int u_orientation;
|
|
458
|
+
|
|
459
|
+
void main() {
|
|
460
|
+
float groupWidth = a_bar.z * ${BAR_GROUP_WIDTH_RATIO.toFixed(1)};
|
|
461
|
+
float sliceWidth = groupWidth / max(1.0, u_seriesCount);
|
|
462
|
+
float center = a_bar.x - groupWidth * 0.5
|
|
463
|
+
+ sliceWidth * (u_seriesIndex + 0.5);
|
|
464
|
+
float halfWidth = sliceWidth * ${(BAR_SLICE_WIDTH_RATIO / 2).toFixed(2)};
|
|
465
|
+
float category = mix(center - halfWidth, center + halfWidth, a_unit.x);
|
|
466
|
+
float value = mix(u_baseline, a_bar.y, a_unit.y);
|
|
467
|
+
float categoryDenom = max(
|
|
468
|
+
0.000000000001,
|
|
469
|
+
abs(u_categoryRange.y - u_categoryRange.x)
|
|
470
|
+
);
|
|
471
|
+
float valueDenom = max(
|
|
472
|
+
0.000000000001,
|
|
473
|
+
abs(u_valueRange.y - u_valueRange.x)
|
|
474
|
+
);
|
|
475
|
+
float categoryRatio = (category - u_categoryRange.x) / categoryDenom;
|
|
476
|
+
float valueRatio = (value - u_valueRange.x) / valueDenom;
|
|
477
|
+
vec2 px = u_orientation == 0
|
|
478
|
+
? vec2(
|
|
479
|
+
u_rect.x + categoryRatio * u_rect.z,
|
|
480
|
+
u_rect.y + (1.0 - valueRatio) * u_rect.w
|
|
481
|
+
)
|
|
482
|
+
: vec2(
|
|
483
|
+
u_rect.x + valueRatio * u_rect.z,
|
|
484
|
+
u_rect.y + categoryRatio * u_rect.w
|
|
485
|
+
);
|
|
486
|
+
vec2 clip = (px / u_resolution) * 2.0 - 1.0;
|
|
487
|
+
gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
|
|
488
|
+
}
|
|
489
|
+
`;
|
|
490
|
+
var FRAGMENT_SOURCE = `#version 300 es
|
|
491
|
+
precision highp float;
|
|
492
|
+
uniform vec3 u_color;
|
|
493
|
+
out vec4 outColor;
|
|
494
|
+
|
|
495
|
+
void main() {
|
|
496
|
+
outColor = vec4(u_color, 1.0);
|
|
497
|
+
}
|
|
498
|
+
`;
|
|
499
|
+
var getBarCategoryInterval = ({
|
|
500
|
+
visiblePoints,
|
|
501
|
+
index,
|
|
502
|
+
categoryMin,
|
|
503
|
+
categoryMax
|
|
504
|
+
}) => {
|
|
505
|
+
const count = visiblePoints.pointCount;
|
|
506
|
+
const category = visiblePoints.x[index];
|
|
507
|
+
const fallbackInterval = (categoryMax - categoryMin) / Math.max(10, count || 1);
|
|
508
|
+
const previousGap = index > 0 ? category - visiblePoints.x[index - 1] : Infinity;
|
|
509
|
+
const nextGap = index + 1 < count ? visiblePoints.x[index + 1] - category : Infinity;
|
|
510
|
+
let interval = Math.min(
|
|
511
|
+
previousGap > 0 ? previousGap : Infinity,
|
|
512
|
+
nextGap > 0 ? nextGap : Infinity
|
|
513
|
+
);
|
|
514
|
+
if (!Number.isFinite(interval)) {
|
|
515
|
+
interval = Number.isFinite(previousGap) && previousGap > 0 ? previousGap : Number.isFinite(nextGap) && nextGap > 0 ? nextGap : fallbackInterval;
|
|
516
|
+
}
|
|
517
|
+
return Math.max(Number.EPSILON, interval || fallbackInterval || 1);
|
|
518
|
+
};
|
|
519
|
+
var getGroupedBarCategory = ({
|
|
520
|
+
visiblePoints,
|
|
521
|
+
pointIndex,
|
|
522
|
+
seriesIndex,
|
|
523
|
+
seriesCount,
|
|
524
|
+
categoryMin,
|
|
525
|
+
categoryMax
|
|
526
|
+
}) => {
|
|
527
|
+
const category = visiblePoints.x[pointIndex];
|
|
528
|
+
const interval = getBarCategoryInterval({
|
|
529
|
+
visiblePoints,
|
|
530
|
+
index: pointIndex,
|
|
531
|
+
categoryMin,
|
|
532
|
+
categoryMax
|
|
533
|
+
});
|
|
534
|
+
const groupWidth = interval * BAR_GROUP_WIDTH_RATIO;
|
|
535
|
+
const sliceWidth = groupWidth / Math.max(1, seriesCount);
|
|
536
|
+
return category - groupWidth / 2 + sliceWidth * (seriesIndex + 0.5);
|
|
537
|
+
};
|
|
538
|
+
var fillBarInstances = ({
|
|
539
|
+
vertices,
|
|
540
|
+
visiblePoints,
|
|
541
|
+
categoryMin,
|
|
542
|
+
categoryMax
|
|
543
|
+
}) => {
|
|
544
|
+
for (let index = 0; index < visiblePoints.pointCount; index += 1) {
|
|
545
|
+
const offset = index * BAR_VERTEX_FLOAT_STRIDE;
|
|
546
|
+
vertices[offset] = visiblePoints.x[index];
|
|
547
|
+
vertices[offset + 1] = visiblePoints.y[index];
|
|
548
|
+
vertices[offset + 2] = getBarCategoryInterval({
|
|
549
|
+
visiblePoints,
|
|
550
|
+
index,
|
|
551
|
+
categoryMin,
|
|
552
|
+
categoryMax
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
return visiblePoints.pointCount;
|
|
556
|
+
};
|
|
557
|
+
var createBarRenderer = (gl) => {
|
|
558
|
+
const program = createProgram(gl, VERTEX_SOURCE, FRAGMENT_SOURCE);
|
|
559
|
+
const quadBuffer = gl.createBuffer();
|
|
560
|
+
const buffers = /* @__PURE__ */ new Map();
|
|
561
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
|
562
|
+
gl.bufferData(
|
|
563
|
+
gl.ARRAY_BUFFER,
|
|
564
|
+
new Float32Array([
|
|
565
|
+
0,
|
|
566
|
+
0,
|
|
567
|
+
1,
|
|
568
|
+
0,
|
|
569
|
+
0,
|
|
570
|
+
1,
|
|
571
|
+
0,
|
|
572
|
+
1,
|
|
573
|
+
1,
|
|
574
|
+
0,
|
|
575
|
+
1,
|
|
576
|
+
1
|
|
577
|
+
]),
|
|
578
|
+
gl.STATIC_DRAW
|
|
579
|
+
);
|
|
580
|
+
const locations = Object.freeze({
|
|
581
|
+
aBar: gl.getAttribLocation(program, "a_bar"),
|
|
582
|
+
aUnit: gl.getAttribLocation(program, "a_unit"),
|
|
583
|
+
uBaseline: gl.getUniformLocation(program, "u_baseline"),
|
|
584
|
+
uCategoryRange: gl.getUniformLocation(program, "u_categoryRange"),
|
|
585
|
+
uColor: gl.getUniformLocation(program, "u_color"),
|
|
586
|
+
uOrientation: gl.getUniformLocation(program, "u_orientation"),
|
|
587
|
+
uRect: gl.getUniformLocation(program, "u_rect"),
|
|
588
|
+
uResolution: gl.getUniformLocation(program, "u_resolution"),
|
|
589
|
+
uSeriesCount: gl.getUniformLocation(program, "u_seriesCount"),
|
|
590
|
+
uSeriesIndex: gl.getUniformLocation(program, "u_seriesIndex"),
|
|
591
|
+
uValueRange: gl.getUniformLocation(program, "u_valueRange")
|
|
592
|
+
});
|
|
593
|
+
return {
|
|
594
|
+
draw({
|
|
595
|
+
categoryPixelLength,
|
|
596
|
+
chart,
|
|
597
|
+
descriptor,
|
|
598
|
+
pixelHeight,
|
|
599
|
+
pixelWidth,
|
|
600
|
+
scaledPlot,
|
|
601
|
+
seriesOrderByChart,
|
|
602
|
+
state,
|
|
603
|
+
yRange
|
|
604
|
+
}) {
|
|
605
|
+
gl.disable(gl.BLEND);
|
|
606
|
+
gl.useProgram(program);
|
|
607
|
+
gl.enableVertexAttribArray(locations.aUnit);
|
|
608
|
+
gl.enableVertexAttribArray(locations.aBar);
|
|
609
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
|
610
|
+
gl.vertexAttribPointer(locations.aUnit, 2, gl.FLOAT, false, 0, 0);
|
|
611
|
+
gl.vertexAttribDivisor(locations.aUnit, 0);
|
|
612
|
+
gl.uniform2f(locations.uResolution, pixelWidth, pixelHeight);
|
|
613
|
+
gl.uniform4f(
|
|
614
|
+
locations.uRect,
|
|
615
|
+
scaledPlot.x,
|
|
616
|
+
scaledPlot.y,
|
|
617
|
+
scaledPlot.width,
|
|
618
|
+
scaledPlot.height
|
|
619
|
+
);
|
|
620
|
+
gl.uniform2f(
|
|
621
|
+
locations.uCategoryRange,
|
|
622
|
+
state.xMin,
|
|
623
|
+
state.xMax
|
|
624
|
+
);
|
|
625
|
+
gl.uniform2f(
|
|
626
|
+
locations.uValueRange,
|
|
627
|
+
yRange.minY,
|
|
628
|
+
yRange.maxY
|
|
629
|
+
);
|
|
630
|
+
gl.uniform1f(locations.uBaseline, 0);
|
|
631
|
+
gl.uniform1i(
|
|
632
|
+
locations.uOrientation,
|
|
633
|
+
descriptor.orientation === "horizontal" ? 1 : 0
|
|
634
|
+
);
|
|
635
|
+
const orderedSeries = getOrderedSeries(chart, seriesOrderByChart);
|
|
636
|
+
gl.uniform1f(locations.uSeriesCount, Math.max(1, orderedSeries.length));
|
|
637
|
+
orderedSeries.forEach((series, seriesIndex) => {
|
|
638
|
+
const visiblePoints = series.getVisiblePoints(
|
|
639
|
+
state.xMin,
|
|
640
|
+
state.xMax,
|
|
641
|
+
categoryPixelLength
|
|
642
|
+
);
|
|
643
|
+
if (!visiblePoints.pointCount) return;
|
|
644
|
+
const requiredFloats = visiblePoints.pointCount * BAR_VERTEX_FLOAT_STRIDE;
|
|
645
|
+
const bufferEntry = getDynamicBuffer(
|
|
646
|
+
gl,
|
|
647
|
+
buffers,
|
|
648
|
+
series.id,
|
|
649
|
+
requiredFloats
|
|
650
|
+
);
|
|
651
|
+
const instanceCount = fillBarInstances({
|
|
652
|
+
vertices: bufferEntry.vertices,
|
|
653
|
+
visiblePoints,
|
|
654
|
+
categoryMin: state.xMin,
|
|
655
|
+
categoryMax: state.xMax
|
|
656
|
+
});
|
|
657
|
+
const [r, g, b] = hexToRgb(series.color);
|
|
658
|
+
gl.uniform3f(locations.uColor, r, g, b);
|
|
659
|
+
gl.uniform1f(locations.uSeriesIndex, seriesIndex);
|
|
660
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, bufferEntry.buffer);
|
|
661
|
+
gl.vertexAttribPointer(
|
|
662
|
+
locations.aBar,
|
|
663
|
+
BAR_VERTEX_FLOAT_STRIDE,
|
|
664
|
+
gl.FLOAT,
|
|
665
|
+
false,
|
|
666
|
+
0,
|
|
667
|
+
0
|
|
668
|
+
);
|
|
669
|
+
gl.vertexAttribDivisor(locations.aBar, 1);
|
|
670
|
+
gl.bufferSubData(
|
|
671
|
+
gl.ARRAY_BUFFER,
|
|
672
|
+
0,
|
|
673
|
+
bufferEntry.vertices,
|
|
674
|
+
0,
|
|
675
|
+
instanceCount * BAR_VERTEX_FLOAT_STRIDE
|
|
676
|
+
);
|
|
677
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
|
|
678
|
+
});
|
|
679
|
+
gl.vertexAttribDivisor(locations.aBar, 0);
|
|
680
|
+
return { seriesEndpoints: /* @__PURE__ */ new Map() };
|
|
681
|
+
},
|
|
682
|
+
getTooltipCategory(payload) {
|
|
683
|
+
return getGroupedBarCategory(payload);
|
|
684
|
+
},
|
|
685
|
+
destroy() {
|
|
686
|
+
deleteBufferCache(gl, buffers);
|
|
687
|
+
gl.deleteBuffer(quadBuffer);
|
|
688
|
+
gl.deleteProgram(program);
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
};
|
|
692
|
+
|
|
175
693
|
// src/core/lodSeries.js
|
|
176
694
|
var DEFAULT_MAX_LEVELS = 18;
|
|
177
695
|
var toTypedArray = (value, Type) => {
|
|
@@ -250,7 +768,7 @@ var concatTyped = (Type, left, right) => {
|
|
|
250
768
|
out.set(right, left.length);
|
|
251
769
|
return out;
|
|
252
770
|
};
|
|
253
|
-
var
|
|
771
|
+
var LodSeries = class {
|
|
254
772
|
constructor({ id, name, color, x, y, maxLevels = DEFAULT_MAX_LEVELS }) {
|
|
255
773
|
this.id = id;
|
|
256
774
|
this.name = name || id;
|
|
@@ -365,273 +883,182 @@ var LineSeries = class {
|
|
|
365
883
|
};
|
|
366
884
|
}
|
|
367
885
|
};
|
|
368
|
-
var
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
var PLOT_PADDING = {
|
|
374
|
-
left: 38,
|
|
375
|
-
right: RIGHT_AXIS_WIDTH,
|
|
376
|
-
top: 34,
|
|
377
|
-
bottom: 28
|
|
886
|
+
var LineSeries = class extends LodSeries {
|
|
887
|
+
constructor(options) {
|
|
888
|
+
super(options);
|
|
889
|
+
this.type = "line";
|
|
890
|
+
}
|
|
378
891
|
};
|
|
379
|
-
var
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
892
|
+
var BarSeries = class extends LodSeries {
|
|
893
|
+
constructor({ orientation = "vertical", ...options }) {
|
|
894
|
+
super(options);
|
|
895
|
+
if (orientation !== "vertical" && orientation !== "horizontal") {
|
|
896
|
+
throw new TypeError(
|
|
897
|
+
'BarSeries orientation must be "vertical" or "horizontal"'
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
this.type = "bar";
|
|
901
|
+
this.orientation = orientation;
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
var createLineSeries = (options) => new LineSeries(options);
|
|
905
|
+
var createSeries = createLineSeries;
|
|
906
|
+
var createBarSeries = (options) => new BarSeries(options);
|
|
907
|
+
|
|
908
|
+
// src/core/renderers/lineRenderer.js
|
|
387
909
|
var AA_VERTEX_FLOAT_STRIDE = 6;
|
|
388
910
|
var AA_LINE_WIDTH_PX = 1.5;
|
|
389
911
|
var AA_EDGE_WIDTH_PX = 1;
|
|
390
912
|
var DASH_LENGTH_PX = 7;
|
|
391
913
|
var DASH_GAP_PX = 5;
|
|
392
|
-
var
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return [
|
|
399
|
-
(number >> 16 & 255) / 255,
|
|
400
|
-
(number >> 8 & 255) / 255,
|
|
401
|
-
(number & 255) / 255
|
|
402
|
-
];
|
|
403
|
-
};
|
|
404
|
-
var getReadableTextColor = (backgroundColor) => {
|
|
405
|
-
const [r, g, b] = hexToRgb(backgroundColor);
|
|
406
|
-
const toLinear = (value) => value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
407
|
-
const luminance = 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
408
|
-
return luminance > 0.45 ? "#111827" : "#ffffff";
|
|
409
|
-
};
|
|
410
|
-
var getSeriesLabelTextColor = (series) => {
|
|
411
|
-
if (series.__labelTextColorFor !== series.color) {
|
|
412
|
-
series.__labelTextColorFor = series.color;
|
|
413
|
-
series.__labelTextColor = getReadableTextColor(series.color);
|
|
414
|
-
}
|
|
415
|
-
return series.__labelTextColor;
|
|
416
|
-
};
|
|
417
|
-
var createProgram = (gl) => {
|
|
418
|
-
const vertexSource = `#version 300 es
|
|
419
|
-
in vec2 a_xy;
|
|
420
|
-
uniform vec2 u_resolution;
|
|
421
|
-
uniform vec4 u_rect;
|
|
422
|
-
uniform vec2 u_xRange;
|
|
423
|
-
uniform vec2 u_yRange;
|
|
914
|
+
var LINE_VERTEX_SOURCE = `#version 300 es
|
|
915
|
+
in vec2 a_xy;
|
|
916
|
+
uniform vec2 u_resolution;
|
|
917
|
+
uniform vec4 u_rect;
|
|
918
|
+
uniform vec2 u_xRange;
|
|
919
|
+
uniform vec2 u_yRange;
|
|
424
920
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
out vec4 outColor;
|
|
921
|
+
void main() {
|
|
922
|
+
float xDenom = max(0.000000000001, abs(u_xRange.y - u_xRange.x));
|
|
923
|
+
float yDenom = max(0.000000000001, abs(u_yRange.y - u_yRange.x));
|
|
924
|
+
float nx = (a_xy.x - u_xRange.x) / xDenom;
|
|
925
|
+
float ny = (a_xy.y - u_yRange.x) / yDenom;
|
|
926
|
+
float px = u_rect.x + nx * u_rect.z;
|
|
927
|
+
float py = u_rect.y + (1.0 - ny) * u_rect.w;
|
|
928
|
+
vec2 clip = (vec2(px, py) / u_resolution) * 2.0 - 1.0;
|
|
929
|
+
gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
|
|
930
|
+
}
|
|
931
|
+
`;
|
|
932
|
+
var LINE_FRAGMENT_SOURCE = `#version 300 es
|
|
933
|
+
precision highp float;
|
|
934
|
+
uniform vec3 u_color;
|
|
935
|
+
out vec4 outColor;
|
|
441
936
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
gl.linkProgram(program);
|
|
459
|
-
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
460
|
-
throw new Error(gl.getProgramInfoLog(program) || "Program link failed");
|
|
461
|
-
}
|
|
462
|
-
return program;
|
|
463
|
-
};
|
|
464
|
-
var createAntialiasProgram = (gl) => {
|
|
465
|
-
const vertexSource = `#version 300 es
|
|
466
|
-
in vec2 a_start;
|
|
467
|
-
in vec2 a_end;
|
|
468
|
-
in float a_side;
|
|
469
|
-
in float a_along;
|
|
470
|
-
uniform vec2 u_resolution;
|
|
471
|
-
uniform vec4 u_rect;
|
|
472
|
-
uniform vec2 u_xRange;
|
|
473
|
-
uniform vec2 u_yRange;
|
|
474
|
-
uniform float u_lineHalfWidth;
|
|
475
|
-
uniform float u_edgeWidth;
|
|
476
|
-
out float v_side;
|
|
937
|
+
void main() {
|
|
938
|
+
outColor = vec4(u_color, 1.0);
|
|
939
|
+
}
|
|
940
|
+
`;
|
|
941
|
+
var AA_VERTEX_SOURCE = `#version 300 es
|
|
942
|
+
in vec2 a_start;
|
|
943
|
+
in vec2 a_end;
|
|
944
|
+
in float a_side;
|
|
945
|
+
in float a_along;
|
|
946
|
+
uniform vec2 u_resolution;
|
|
947
|
+
uniform vec4 u_rect;
|
|
948
|
+
uniform vec2 u_xRange;
|
|
949
|
+
uniform vec2 u_yRange;
|
|
950
|
+
uniform float u_lineHalfWidth;
|
|
951
|
+
uniform float u_edgeWidth;
|
|
952
|
+
out float v_side;
|
|
477
953
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
954
|
+
vec2 toPixel(vec2 value) {
|
|
955
|
+
float xDenom = max(0.000000000001, abs(u_xRange.y - u_xRange.x));
|
|
956
|
+
float yDenom = max(0.000000000001, abs(u_yRange.y - u_yRange.x));
|
|
957
|
+
float nx = (value.x - u_xRange.x) / xDenom;
|
|
958
|
+
float ny = (value.y - u_yRange.x) / yDenom;
|
|
959
|
+
return vec2(
|
|
960
|
+
u_rect.x + nx * u_rect.z,
|
|
961
|
+
u_rect.y + (1.0 - ny) * u_rect.w
|
|
962
|
+
);
|
|
963
|
+
}
|
|
488
964
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
out vec4 outColor;
|
|
965
|
+
void main() {
|
|
966
|
+
vec2 startPx = toPixel(a_start);
|
|
967
|
+
vec2 endPx = toPixel(a_end);
|
|
968
|
+
vec2 delta = endPx - startPx;
|
|
969
|
+
float segmentLength = max(0.000001, length(delta));
|
|
970
|
+
vec2 direction = delta / segmentLength;
|
|
971
|
+
vec2 normal = vec2(-direction.y, direction.x);
|
|
972
|
+
float expand = u_lineHalfWidth + u_edgeWidth;
|
|
973
|
+
float cap = min(expand, segmentLength * 0.5);
|
|
974
|
+
vec2 px = mix(startPx, endPx, a_along)
|
|
975
|
+
+ direction * ((a_along * 2.0 - 1.0) * cap)
|
|
976
|
+
+ normal * a_side * expand;
|
|
977
|
+
vec2 clip = (px / u_resolution) * 2.0 - 1.0;
|
|
978
|
+
v_side = a_side;
|
|
979
|
+
gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
|
|
980
|
+
}
|
|
981
|
+
`;
|
|
982
|
+
var AA_FRAGMENT_SOURCE = `#version 300 es
|
|
983
|
+
precision highp float;
|
|
984
|
+
uniform vec3 u_color;
|
|
985
|
+
uniform float u_lineHalfWidth;
|
|
986
|
+
uniform float u_edgeWidth;
|
|
987
|
+
in float v_side;
|
|
988
|
+
out vec4 outColor;
|
|
514
989
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
gl.attachShader(program, compile(gl.VERTEX_SHADER, vertexSource));
|
|
533
|
-
gl.attachShader(program, compile(gl.FRAGMENT_SHADER, fragmentSource));
|
|
534
|
-
gl.linkProgram(program);
|
|
535
|
-
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
536
|
-
throw new Error(gl.getProgramInfoLog(program) || "Program link failed");
|
|
537
|
-
}
|
|
538
|
-
return program;
|
|
539
|
-
};
|
|
540
|
-
var createSeriesBufferCache = () => /* @__PURE__ */ new Map();
|
|
541
|
-
var getNextVertexCapacity = (currentCapacity, requiredCapacity) => {
|
|
542
|
-
let nextCapacity = Math.max(
|
|
543
|
-
INITIAL_GPU_VERTEX_CAPACITY,
|
|
544
|
-
currentCapacity || 0
|
|
545
|
-
);
|
|
546
|
-
while (nextCapacity < requiredCapacity) {
|
|
547
|
-
nextCapacity *= 2;
|
|
548
|
-
}
|
|
549
|
-
return nextCapacity;
|
|
550
|
-
};
|
|
551
|
-
var getSeriesBuffer = (gl, cache, seriesId, pointCount) => {
|
|
552
|
-
const requiredVertexFloats = pointCount * 2;
|
|
553
|
-
let entry = cache.get(seriesId);
|
|
554
|
-
if (!entry) {
|
|
555
|
-
entry = {
|
|
556
|
-
buffer: gl.createBuffer(),
|
|
557
|
-
gpuCapacity: 0,
|
|
558
|
-
vertices: new Float32Array(
|
|
559
|
-
getNextVertexCapacity(0, requiredVertexFloats)
|
|
560
|
-
)
|
|
561
|
-
};
|
|
562
|
-
cache.set(seriesId, entry);
|
|
563
|
-
}
|
|
564
|
-
if (entry.vertices.length < requiredVertexFloats) {
|
|
565
|
-
entry.vertices = new Float32Array(
|
|
566
|
-
getNextVertexCapacity(entry.vertices.length, requiredVertexFloats)
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
if (entry.gpuCapacity < entry.vertices.length) {
|
|
570
|
-
entry.gpuCapacity = entry.vertices.length;
|
|
571
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, entry.buffer);
|
|
572
|
-
gl.bufferData(
|
|
573
|
-
gl.ARRAY_BUFFER,
|
|
574
|
-
entry.gpuCapacity * Float32Array.BYTES_PER_ELEMENT,
|
|
575
|
-
gl.DYNAMIC_DRAW
|
|
576
|
-
);
|
|
990
|
+
void main() {
|
|
991
|
+
float expand = u_lineHalfWidth + u_edgeWidth;
|
|
992
|
+
float distanceFromCenter = abs(v_side) * expand;
|
|
993
|
+
float alpha = 1.0 - smoothstep(u_lineHalfWidth, expand, distanceFromCenter);
|
|
994
|
+
outColor = vec4(u_color, alpha);
|
|
995
|
+
}
|
|
996
|
+
`;
|
|
997
|
+
var fillSeriesPoints = ({
|
|
998
|
+
vertices,
|
|
999
|
+
visiblePoints,
|
|
1000
|
+
pointCount,
|
|
1001
|
+
animatedPoint
|
|
1002
|
+
}) => {
|
|
1003
|
+
for (let index = 0; index < pointCount; index += 1) {
|
|
1004
|
+
const source = animatedPoint && index === animatedPoint.index ? animatedPoint : { x: visiblePoints.x[index], y: visiblePoints.y[index] };
|
|
1005
|
+
vertices[index * 2] = source.x;
|
|
1006
|
+
vertices[index * 2 + 1] = source.y;
|
|
577
1007
|
}
|
|
578
|
-
return entry;
|
|
579
1008
|
};
|
|
580
|
-
var
|
|
581
|
-
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
if (
|
|
585
|
-
|
|
586
|
-
buffer: gl.createBuffer(),
|
|
587
|
-
gpuCapacity: 0,
|
|
588
|
-
vertices: new Float32Array(
|
|
589
|
-
getNextVertexCapacity(0, requiredVertexFloats)
|
|
590
|
-
)
|
|
591
|
-
};
|
|
592
|
-
cache.set(seriesId, entry);
|
|
593
|
-
}
|
|
594
|
-
if (entry.vertices.length < requiredVertexFloats) {
|
|
595
|
-
entry.vertices = new Float32Array(
|
|
596
|
-
getNextVertexCapacity(entry.vertices.length, requiredVertexFloats)
|
|
597
|
-
);
|
|
598
|
-
}
|
|
599
|
-
if (entry.gpuCapacity < entry.vertices.length) {
|
|
600
|
-
entry.gpuCapacity = entry.vertices.length;
|
|
601
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, entry.buffer);
|
|
602
|
-
gl.bufferData(
|
|
603
|
-
gl.ARRAY_BUFFER,
|
|
604
|
-
entry.gpuCapacity * Float32Array.BYTES_PER_ELEMENT,
|
|
605
|
-
gl.DYNAMIC_DRAW
|
|
606
|
-
);
|
|
1009
|
+
var getSinglePointSegment = ({ series, state, yRange, plot }) => {
|
|
1010
|
+
if (series.length !== 1) return null;
|
|
1011
|
+
const x = series.rawX[0];
|
|
1012
|
+
const y = series.rawY[0];
|
|
1013
|
+
if (x < state.xMin || x > state.xMax || y < yRange.minY || y > yRange.maxY) {
|
|
1014
|
+
return null;
|
|
607
1015
|
}
|
|
608
|
-
|
|
1016
|
+
const halfWidth = (state.xMax - state.xMin) / Math.max(1, plot.width) * 3;
|
|
1017
|
+
return {
|
|
1018
|
+
x: new Float64Array([x - halfWidth, x + halfWidth]),
|
|
1019
|
+
y: new Float32Array([y, y]),
|
|
1020
|
+
bucketSize: 1,
|
|
1021
|
+
pointCount: 2,
|
|
1022
|
+
endpoint: { x, y }
|
|
1023
|
+
};
|
|
609
1024
|
};
|
|
610
|
-
var
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
1025
|
+
var writeAntialiasVertex = (vertices, offset, start, end, side, along) => {
|
|
1026
|
+
vertices[offset] = start.x;
|
|
1027
|
+
vertices[offset + 1] = start.y;
|
|
1028
|
+
vertices[offset + 2] = end.x;
|
|
1029
|
+
vertices[offset + 3] = end.y;
|
|
1030
|
+
vertices[offset + 4] = side;
|
|
1031
|
+
vertices[offset + 5] = along;
|
|
615
1032
|
};
|
|
616
|
-
var
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
1033
|
+
var getSeriesPoint = (visiblePoints, animatedPoint, index) => animatedPoint && index === animatedPoint.index ? { x: animatedPoint.x, y: animatedPoint.y } : { x: visiblePoints.x[index], y: visiblePoints.y[index] };
|
|
1034
|
+
var fillAntialiasSeriesSegments = ({
|
|
1035
|
+
vertices,
|
|
1036
|
+
visiblePoints,
|
|
1037
|
+
pointCount,
|
|
1038
|
+
animatedPoint
|
|
1039
|
+
}) => {
|
|
1040
|
+
let offset = 0;
|
|
1041
|
+
for (let index = 0; index < pointCount - 1; index += 1) {
|
|
1042
|
+
const start = getSeriesPoint(visiblePoints, animatedPoint, index);
|
|
1043
|
+
const end = getSeriesPoint(visiblePoints, animatedPoint, index + 1);
|
|
1044
|
+
[
|
|
1045
|
+
[-1, 0],
|
|
1046
|
+
[1, 0],
|
|
1047
|
+
[-1, 1],
|
|
1048
|
+
[-1, 1],
|
|
1049
|
+
[1, 0],
|
|
1050
|
+
[1, 1]
|
|
1051
|
+
].forEach(([side, along]) => {
|
|
1052
|
+
writeAntialiasVertex(vertices, offset, start, end, side, along);
|
|
1053
|
+
offset += AA_VERTEX_FLOAT_STRIDE;
|
|
1054
|
+
});
|
|
625
1055
|
}
|
|
1056
|
+
return offset / AA_VERTEX_FLOAT_STRIDE;
|
|
626
1057
|
};
|
|
627
1058
|
var projectDataToPixel = ({ x, y, state, yRange, plot }) => ({
|
|
628
1059
|
x: plot.x + (x - state.xMin) / (state.xMax - state.xMin) * plot.width,
|
|
629
1060
|
y: plot.y + (yRange.maxY - y) / (yRange.maxY - yRange.minY) * plot.height
|
|
630
1061
|
});
|
|
631
|
-
var writeLineVertex = (vertices, offset, x, y) => {
|
|
632
|
-
vertices[offset] = x;
|
|
633
|
-
vertices[offset + 1] = y;
|
|
634
|
-
};
|
|
635
1062
|
var fillDashedSeriesSegments = ({
|
|
636
1063
|
vertices,
|
|
637
1064
|
visiblePoints,
|
|
@@ -643,14 +1070,11 @@ var fillDashedSeriesSegments = ({
|
|
|
643
1070
|
let offset = 0;
|
|
644
1071
|
let distance = 0;
|
|
645
1072
|
const dashTotal = DASH_LENGTH_PX + DASH_GAP_PX;
|
|
646
|
-
for (let
|
|
647
|
-
const start = {
|
|
648
|
-
x: visiblePoints.x[i],
|
|
649
|
-
y: visiblePoints.y[i]
|
|
650
|
-
};
|
|
1073
|
+
for (let index = 0; index < pointCount - 1; index += 1) {
|
|
1074
|
+
const start = { x: visiblePoints.x[index], y: visiblePoints.y[index] };
|
|
651
1075
|
const end = {
|
|
652
|
-
x: visiblePoints.x[
|
|
653
|
-
y: visiblePoints.y[
|
|
1076
|
+
x: visiblePoints.x[index + 1],
|
|
1077
|
+
y: visiblePoints.y[index + 1]
|
|
654
1078
|
};
|
|
655
1079
|
const startPx = projectDataToPixel({ ...start, state, yRange, plot });
|
|
656
1080
|
const endPx = projectDataToPixel({ ...end, state, yRange, plot });
|
|
@@ -662,25 +1086,14 @@ var fillDashedSeriesSegments = ({
|
|
|
662
1086
|
const boundary = phase < DASH_LENGTH_PX ? DASH_LENGTH_PX - phase : dashTotal - phase;
|
|
663
1087
|
const step = Math.min(boundary, pixelLength - consumed);
|
|
664
1088
|
if (phase < DASH_LENGTH_PX) {
|
|
665
|
-
if (offset + 4 > vertices.length)
|
|
666
|
-
return offset / 2;
|
|
667
|
-
}
|
|
1089
|
+
if (offset + 4 > vertices.length) return offset / 2;
|
|
668
1090
|
const t0 = consumed / pixelLength;
|
|
669
1091
|
const t1 = (consumed + step) / pixelLength;
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
);
|
|
676
|
-
offset += 2;
|
|
677
|
-
writeLineVertex(
|
|
678
|
-
vertices,
|
|
679
|
-
offset,
|
|
680
|
-
start.x + (end.x - start.x) * t1,
|
|
681
|
-
start.y + (end.y - start.y) * t1
|
|
682
|
-
);
|
|
683
|
-
offset += 2;
|
|
1092
|
+
vertices[offset] = start.x + (end.x - start.x) * t0;
|
|
1093
|
+
vertices[offset + 1] = start.y + (end.y - start.y) * t0;
|
|
1094
|
+
vertices[offset + 2] = start.x + (end.x - start.x) * t1;
|
|
1095
|
+
vertices[offset + 3] = start.y + (end.y - start.y) * t1;
|
|
1096
|
+
offset += 4;
|
|
684
1097
|
}
|
|
685
1098
|
consumed += step;
|
|
686
1099
|
distance += step;
|
|
@@ -688,107 +1101,6 @@ var fillDashedSeriesSegments = ({
|
|
|
688
1101
|
}
|
|
689
1102
|
return offset / 2;
|
|
690
1103
|
};
|
|
691
|
-
var getSeriesPoint = (visiblePoints, animatedPoint, index) => {
|
|
692
|
-
if (animatedPoint && index === animatedPoint.index) {
|
|
693
|
-
return { x: animatedPoint.x, y: animatedPoint.y };
|
|
694
|
-
}
|
|
695
|
-
return { x: visiblePoints.x[index], y: visiblePoints.y[index] };
|
|
696
|
-
};
|
|
697
|
-
var getSinglePointSegment = ({ series, state, yRange, plot }) => {
|
|
698
|
-
if (series.length !== 1) return null;
|
|
699
|
-
const x = series.rawX[0];
|
|
700
|
-
const y = series.rawY[0];
|
|
701
|
-
if (x < state.xMin || x > state.xMax || y < yRange.minY || y > yRange.maxY) {
|
|
702
|
-
return null;
|
|
703
|
-
}
|
|
704
|
-
const halfWidth = (state.xMax - state.xMin) / Math.max(1, plot.width) * 3;
|
|
705
|
-
return {
|
|
706
|
-
x: new Float64Array([x - halfWidth, x + halfWidth]),
|
|
707
|
-
y: new Float32Array([y, y]),
|
|
708
|
-
bucketSize: 1,
|
|
709
|
-
pointCount: 2,
|
|
710
|
-
endpoint: { x, y }
|
|
711
|
-
};
|
|
712
|
-
};
|
|
713
|
-
var writeAntialiasVertex = (vertices, offset, start, end, side, along) => {
|
|
714
|
-
vertices[offset] = start.x;
|
|
715
|
-
vertices[offset + 1] = start.y;
|
|
716
|
-
vertices[offset + 2] = end.x;
|
|
717
|
-
vertices[offset + 3] = end.y;
|
|
718
|
-
vertices[offset + 4] = side;
|
|
719
|
-
vertices[offset + 5] = along;
|
|
720
|
-
};
|
|
721
|
-
var fillAntialiasSeriesSegments = ({
|
|
722
|
-
vertices,
|
|
723
|
-
visiblePoints,
|
|
724
|
-
pointCount,
|
|
725
|
-
animatedPoint
|
|
726
|
-
}) => {
|
|
727
|
-
let offset = 0;
|
|
728
|
-
for (let i = 0; i < pointCount - 1; i += 1) {
|
|
729
|
-
const start = getSeriesPoint(visiblePoints, animatedPoint, i);
|
|
730
|
-
const end = getSeriesPoint(visiblePoints, animatedPoint, i + 1);
|
|
731
|
-
writeAntialiasVertex(vertices, offset, start, end, -1, 0);
|
|
732
|
-
offset += AA_VERTEX_FLOAT_STRIDE;
|
|
733
|
-
writeAntialiasVertex(vertices, offset, start, end, 1, 0);
|
|
734
|
-
offset += AA_VERTEX_FLOAT_STRIDE;
|
|
735
|
-
writeAntialiasVertex(vertices, offset, start, end, -1, 1);
|
|
736
|
-
offset += AA_VERTEX_FLOAT_STRIDE;
|
|
737
|
-
writeAntialiasVertex(vertices, offset, start, end, -1, 1);
|
|
738
|
-
offset += AA_VERTEX_FLOAT_STRIDE;
|
|
739
|
-
writeAntialiasVertex(vertices, offset, start, end, 1, 0);
|
|
740
|
-
offset += AA_VERTEX_FLOAT_STRIDE;
|
|
741
|
-
writeAntialiasVertex(vertices, offset, start, end, 1, 1);
|
|
742
|
-
offset += AA_VERTEX_FLOAT_STRIDE;
|
|
743
|
-
}
|
|
744
|
-
return offset / AA_VERTEX_FLOAT_STRIDE;
|
|
745
|
-
};
|
|
746
|
-
var lowerBound2 = (array, value) => {
|
|
747
|
-
let lo = 0;
|
|
748
|
-
let hi = array.length;
|
|
749
|
-
while (lo < hi) {
|
|
750
|
-
const mid = lo + hi >> 1;
|
|
751
|
-
if (array[mid] < value) lo = mid + 1;
|
|
752
|
-
else hi = mid;
|
|
753
|
-
}
|
|
754
|
-
return lo;
|
|
755
|
-
};
|
|
756
|
-
var getNearestPointIndex = (xValues, xValue) => {
|
|
757
|
-
if (!xValues.length) return -1;
|
|
758
|
-
const nextIndex = lowerBound2(xValues, xValue);
|
|
759
|
-
if (nextIndex <= 0) return 0;
|
|
760
|
-
if (nextIndex >= xValues.length) return xValues.length - 1;
|
|
761
|
-
const previousIndex = nextIndex - 1;
|
|
762
|
-
return Math.abs(xValues[nextIndex] - xValue) < Math.abs(xValue - xValues[previousIndex]) ? nextIndex : previousIndex;
|
|
763
|
-
};
|
|
764
|
-
var getChartXBounds = (chart) => {
|
|
765
|
-
let minX = Infinity;
|
|
766
|
-
let maxX = -Infinity;
|
|
767
|
-
chart.series.forEach((series) => {
|
|
768
|
-
if (series.length === 0) return;
|
|
769
|
-
minX = Math.min(minX, series.rawX[0]);
|
|
770
|
-
maxX = Math.max(maxX, series.rawX[series.length - 1]);
|
|
771
|
-
});
|
|
772
|
-
return { minX, maxX };
|
|
773
|
-
};
|
|
774
|
-
var getOrderedSeries = (chart, seriesOrderByChart) => {
|
|
775
|
-
const series = Array.isArray(chart?.series) ? chart.series : [];
|
|
776
|
-
const order = seriesOrderByChart?.[chart?.id];
|
|
777
|
-
if (!Array.isArray(order) || order.length !== series.length) {
|
|
778
|
-
return series;
|
|
779
|
-
}
|
|
780
|
-
const used = /* @__PURE__ */ new Set();
|
|
781
|
-
const ordered = [];
|
|
782
|
-
order.forEach((seriesIndex) => {
|
|
783
|
-
if (!Number.isInteger(seriesIndex)) return;
|
|
784
|
-
if (seriesIndex < 0 || seriesIndex >= series.length) return;
|
|
785
|
-
if (used.has(seriesIndex)) return;
|
|
786
|
-
used.add(seriesIndex);
|
|
787
|
-
ordered.push(series[seriesIndex]);
|
|
788
|
-
});
|
|
789
|
-
if (ordered.length !== series.length) return series;
|
|
790
|
-
return ordered;
|
|
791
|
-
};
|
|
792
1104
|
var normalizeMovingAverage = (movingAverage) => {
|
|
793
1105
|
if (!movingAverage?.enabled) return null;
|
|
794
1106
|
return {
|
|
@@ -797,7 +1109,6 @@ var normalizeMovingAverage = (movingAverage) => {
|
|
|
797
1109
|
type: movingAverage.type === "sma" ? "sma" : "ema"
|
|
798
1110
|
};
|
|
799
1111
|
};
|
|
800
|
-
var getMovingAverageCacheKey = (series, movingAverage) => `${series.id}::ma:${movingAverage.type}:${movingAverage.period}`;
|
|
801
1112
|
var calculateMovingAverageChunk = ({
|
|
802
1113
|
sourceY,
|
|
803
1114
|
startIndex,
|
|
@@ -806,106 +1117,524 @@ var calculateMovingAverageChunk = ({
|
|
|
806
1117
|
previousAverage
|
|
807
1118
|
}) => {
|
|
808
1119
|
const out = new Float32Array(Math.max(0, sourceY.length - startIndex));
|
|
809
|
-
if (out.length
|
|
1120
|
+
if (!out.length) return out;
|
|
810
1121
|
if (type === "sma") {
|
|
811
1122
|
let sum = 0;
|
|
812
1123
|
const firstWindowStart = Math.max(0, startIndex - period + 1);
|
|
813
|
-
for (let
|
|
814
|
-
sum += sourceY[
|
|
1124
|
+
for (let index = firstWindowStart; index < startIndex; index += 1) {
|
|
1125
|
+
sum += sourceY[index];
|
|
815
1126
|
}
|
|
816
|
-
for (let
|
|
817
|
-
sum += sourceY[
|
|
818
|
-
const removeIndex =
|
|
819
|
-
if (removeIndex >= firstWindowStart)
|
|
820
|
-
|
|
821
|
-
}
|
|
822
|
-
const safePeriod = Math.min(period, sourceIndex + 1);
|
|
823
|
-
out[sourceIndex - startIndex] = sum / safePeriod;
|
|
1127
|
+
for (let index = startIndex; index < sourceY.length; index += 1) {
|
|
1128
|
+
sum += sourceY[index];
|
|
1129
|
+
const removeIndex = index - period;
|
|
1130
|
+
if (removeIndex >= firstWindowStart) sum -= sourceY[removeIndex];
|
|
1131
|
+
out[index - startIndex] = sum / Math.min(period, index + 1);
|
|
824
1132
|
}
|
|
825
1133
|
return out;
|
|
826
1134
|
}
|
|
827
1135
|
const multiplier = 2 / (period + 1);
|
|
828
|
-
let
|
|
829
|
-
for (let
|
|
830
|
-
|
|
831
|
-
out[
|
|
1136
|
+
let previous = Number.isFinite(previousAverage) ? previousAverage : sourceY[startIndex];
|
|
1137
|
+
for (let index = startIndex; index < sourceY.length; index += 1) {
|
|
1138
|
+
previous = sourceY[index] * multiplier + previous * (1 - multiplier);
|
|
1139
|
+
out[index - startIndex] = previous;
|
|
832
1140
|
}
|
|
833
1141
|
return out;
|
|
834
1142
|
};
|
|
835
|
-
var getMovingAverageSeriesForChart = ({
|
|
836
|
-
chart,
|
|
837
|
-
movingAverage,
|
|
838
|
-
cache
|
|
839
|
-
}) => {
|
|
840
|
-
const normalized = normalizeMovingAverage(movingAverage);
|
|
841
|
-
if (!normalized
|
|
842
|
-
return chart.series.map((series) => {
|
|
843
|
-
if (series.length
|
|
844
|
-
const
|
|
845
|
-
const cached = cache.get(
|
|
846
|
-
if (cached && cached.sourceSeries === series && cached.sourceLength <= series.length) {
|
|
847
|
-
const appendStart = cached.sourceLength;
|
|
848
|
-
if (appendStart < series.length) {
|
|
849
|
-
const appendedY = calculateMovingAverageChunk({
|
|
850
|
-
sourceY: series.rawY.subarray(0, series.length),
|
|
851
|
-
startIndex: appendStart,
|
|
852
|
-
period: normalized.period,
|
|
853
|
-
type: normalized.type,
|
|
854
|
-
previousAverage: appendStart > 0 ? cached.series.rawY[appendStart - 1] : void 0
|
|
855
|
-
});
|
|
856
|
-
cached.series.append(
|
|
857
|
-
series.rawX.subarray(appendStart, series.length),
|
|
858
|
-
appendedY
|
|
859
|
-
);
|
|
860
|
-
cached.sourceLength = series.length;
|
|
861
|
-
}
|
|
862
|
-
cached.series.color = series.color;
|
|
863
|
-
cached.series.name = `${series.name} ${normalized.type.toUpperCase()} ${normalized.period}`;
|
|
864
|
-
return cached.series;
|
|
865
|
-
}
|
|
866
|
-
const
|
|
867
|
-
sourceY: series.rawY.subarray(0, series.length),
|
|
868
|
-
startIndex: 0,
|
|
869
|
-
period: normalized.period,
|
|
870
|
-
type: normalized.type
|
|
871
|
-
});
|
|
872
|
-
const
|
|
873
|
-
id:
|
|
874
|
-
name: `${series.name} ${normalized.type.toUpperCase()} ${normalized.period}`,
|
|
875
|
-
color: series.color,
|
|
876
|
-
x: series.rawX.subarray(0, series.length),
|
|
877
|
-
y
|
|
878
|
-
});
|
|
879
|
-
cache.set(
|
|
880
|
-
sourceSeries: series,
|
|
881
|
-
sourceLength: series.length,
|
|
882
|
-
series:
|
|
883
|
-
});
|
|
884
|
-
return
|
|
885
|
-
}).filter(Boolean);
|
|
1143
|
+
var getMovingAverageSeriesForChart = ({
|
|
1144
|
+
chart,
|
|
1145
|
+
movingAverage,
|
|
1146
|
+
cache
|
|
1147
|
+
}) => {
|
|
1148
|
+
const normalized = normalizeMovingAverage(movingAverage);
|
|
1149
|
+
if (!normalized) return [];
|
|
1150
|
+
return chart.series.map((series) => {
|
|
1151
|
+
if (!series.length) return null;
|
|
1152
|
+
const key = `${series.id}::ma:${normalized.type}:${normalized.period}`;
|
|
1153
|
+
const cached = cache.get(key);
|
|
1154
|
+
if (cached && cached.sourceSeries === series && cached.sourceLength <= series.length) {
|
|
1155
|
+
const appendStart = cached.sourceLength;
|
|
1156
|
+
if (appendStart < series.length) {
|
|
1157
|
+
const appendedY = calculateMovingAverageChunk({
|
|
1158
|
+
sourceY: series.rawY.subarray(0, series.length),
|
|
1159
|
+
startIndex: appendStart,
|
|
1160
|
+
period: normalized.period,
|
|
1161
|
+
type: normalized.type,
|
|
1162
|
+
previousAverage: appendStart > 0 ? cached.series.rawY[appendStart - 1] : void 0
|
|
1163
|
+
});
|
|
1164
|
+
cached.series.append(
|
|
1165
|
+
series.rawX.subarray(appendStart, series.length),
|
|
1166
|
+
appendedY
|
|
1167
|
+
);
|
|
1168
|
+
cached.sourceLength = series.length;
|
|
1169
|
+
}
|
|
1170
|
+
cached.series.color = series.color;
|
|
1171
|
+
cached.series.name = `${series.name} ${normalized.type.toUpperCase()} ${normalized.period}`;
|
|
1172
|
+
return cached.series;
|
|
1173
|
+
}
|
|
1174
|
+
const movingY = calculateMovingAverageChunk({
|
|
1175
|
+
sourceY: series.rawY.subarray(0, series.length),
|
|
1176
|
+
startIndex: 0,
|
|
1177
|
+
period: normalized.period,
|
|
1178
|
+
type: normalized.type
|
|
1179
|
+
});
|
|
1180
|
+
const movingSeries = createLineSeries({
|
|
1181
|
+
id: key,
|
|
1182
|
+
name: `${series.name} ${normalized.type.toUpperCase()} ${normalized.period}`,
|
|
1183
|
+
color: series.color,
|
|
1184
|
+
x: series.rawX.subarray(0, series.length),
|
|
1185
|
+
y: movingY
|
|
1186
|
+
});
|
|
1187
|
+
cache.set(key, {
|
|
1188
|
+
sourceSeries: series,
|
|
1189
|
+
sourceLength: series.length,
|
|
1190
|
+
series: movingSeries
|
|
1191
|
+
});
|
|
1192
|
+
return movingSeries;
|
|
1193
|
+
}).filter(Boolean);
|
|
1194
|
+
};
|
|
1195
|
+
var getLineLocations = (gl, program) => Object.freeze({
|
|
1196
|
+
aXy: gl.getAttribLocation(program, "a_xy"),
|
|
1197
|
+
uColor: gl.getUniformLocation(program, "u_color"),
|
|
1198
|
+
uRect: gl.getUniformLocation(program, "u_rect"),
|
|
1199
|
+
uResolution: gl.getUniformLocation(program, "u_resolution"),
|
|
1200
|
+
uXRange: gl.getUniformLocation(program, "u_xRange"),
|
|
1201
|
+
uYRange: gl.getUniformLocation(program, "u_yRange")
|
|
1202
|
+
});
|
|
1203
|
+
var createLineRenderer = (gl) => {
|
|
1204
|
+
const program = createProgram(
|
|
1205
|
+
gl,
|
|
1206
|
+
LINE_VERTEX_SOURCE,
|
|
1207
|
+
LINE_FRAGMENT_SOURCE
|
|
1208
|
+
);
|
|
1209
|
+
const antialiasProgram = createProgram(
|
|
1210
|
+
gl,
|
|
1211
|
+
AA_VERTEX_SOURCE,
|
|
1212
|
+
AA_FRAGMENT_SOURCE
|
|
1213
|
+
);
|
|
1214
|
+
const locations = getLineLocations(gl, program);
|
|
1215
|
+
const aaLocations = Object.freeze({
|
|
1216
|
+
aAlong: gl.getAttribLocation(antialiasProgram, "a_along"),
|
|
1217
|
+
aEnd: gl.getAttribLocation(antialiasProgram, "a_end"),
|
|
1218
|
+
aSide: gl.getAttribLocation(antialiasProgram, "a_side"),
|
|
1219
|
+
aStart: gl.getAttribLocation(antialiasProgram, "a_start"),
|
|
1220
|
+
uColor: gl.getUniformLocation(antialiasProgram, "u_color"),
|
|
1221
|
+
uEdgeWidth: gl.getUniformLocation(antialiasProgram, "u_edgeWidth"),
|
|
1222
|
+
uLineHalfWidth: gl.getUniformLocation(antialiasProgram, "u_lineHalfWidth"),
|
|
1223
|
+
uRect: gl.getUniformLocation(antialiasProgram, "u_rect"),
|
|
1224
|
+
uResolution: gl.getUniformLocation(antialiasProgram, "u_resolution"),
|
|
1225
|
+
uXRange: gl.getUniformLocation(antialiasProgram, "u_xRange"),
|
|
1226
|
+
uYRange: gl.getUniformLocation(antialiasProgram, "u_yRange")
|
|
1227
|
+
});
|
|
1228
|
+
const buffers = /* @__PURE__ */ new Map();
|
|
1229
|
+
const antialiasBuffers = /* @__PURE__ */ new Map();
|
|
1230
|
+
const movingAverageCache = /* @__PURE__ */ new Map();
|
|
1231
|
+
const setCommonUniforms = (activeLocations, pixelWidth, pixelHeight, scaledPlot, state, yRange) => {
|
|
1232
|
+
gl.uniform2f(activeLocations.uResolution, pixelWidth, pixelHeight);
|
|
1233
|
+
gl.uniform4f(
|
|
1234
|
+
activeLocations.uRect,
|
|
1235
|
+
scaledPlot.x,
|
|
1236
|
+
scaledPlot.y,
|
|
1237
|
+
scaledPlot.width,
|
|
1238
|
+
scaledPlot.height
|
|
1239
|
+
);
|
|
1240
|
+
gl.uniform2f(activeLocations.uXRange, state.xMin, state.xMax);
|
|
1241
|
+
gl.uniform2f(activeLocations.uYRange, yRange.minY, yRange.maxY);
|
|
1242
|
+
};
|
|
1243
|
+
return {
|
|
1244
|
+
draw({
|
|
1245
|
+
antialiasLines,
|
|
1246
|
+
chart,
|
|
1247
|
+
descriptor,
|
|
1248
|
+
dpr,
|
|
1249
|
+
getAppendAnimatedPoint,
|
|
1250
|
+
movingAverage,
|
|
1251
|
+
now,
|
|
1252
|
+
pixelHeight,
|
|
1253
|
+
pixelWidth,
|
|
1254
|
+
plot,
|
|
1255
|
+
scaledPlot,
|
|
1256
|
+
seriesOrderByChart,
|
|
1257
|
+
state,
|
|
1258
|
+
yRange
|
|
1259
|
+
}) {
|
|
1260
|
+
const useAntialias = Boolean(antialiasLines);
|
|
1261
|
+
const activeProgram = useAntialias ? antialiasProgram : program;
|
|
1262
|
+
const activeLocations = useAntialias ? aaLocations : locations;
|
|
1263
|
+
gl.useProgram(activeProgram);
|
|
1264
|
+
setCommonUniforms(
|
|
1265
|
+
activeLocations,
|
|
1266
|
+
pixelWidth,
|
|
1267
|
+
pixelHeight,
|
|
1268
|
+
scaledPlot,
|
|
1269
|
+
state,
|
|
1270
|
+
yRange
|
|
1271
|
+
);
|
|
1272
|
+
if (useAntialias) {
|
|
1273
|
+
gl.enable(gl.BLEND);
|
|
1274
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
1275
|
+
gl.enableVertexAttribArray(aaLocations.aStart);
|
|
1276
|
+
gl.enableVertexAttribArray(aaLocations.aEnd);
|
|
1277
|
+
gl.enableVertexAttribArray(aaLocations.aSide);
|
|
1278
|
+
gl.enableVertexAttribArray(aaLocations.aAlong);
|
|
1279
|
+
gl.uniform1f(
|
|
1280
|
+
aaLocations.uLineHalfWidth,
|
|
1281
|
+
AA_LINE_WIDTH_PX * dpr / 2
|
|
1282
|
+
);
|
|
1283
|
+
gl.uniform1f(aaLocations.uEdgeWidth, AA_EDGE_WIDTH_PX * dpr);
|
|
1284
|
+
} else {
|
|
1285
|
+
gl.disable(gl.BLEND);
|
|
1286
|
+
gl.enableVertexAttribArray(locations.aXy);
|
|
1287
|
+
}
|
|
1288
|
+
const seriesEndpoints = /* @__PURE__ */ new Map();
|
|
1289
|
+
const hideBaseSeries = descriptor.capabilities.movingAverage && Boolean(movingAverage?.enabled) && Boolean(movingAverage?.hideBase);
|
|
1290
|
+
if (!hideBaseSeries) {
|
|
1291
|
+
getOrderedSeries(chart, seriesOrderByChart).forEach((series) => {
|
|
1292
|
+
let visiblePoints = series.getVisiblePoints(
|
|
1293
|
+
state.xMin,
|
|
1294
|
+
state.xMax,
|
|
1295
|
+
plot.width
|
|
1296
|
+
);
|
|
1297
|
+
let singlePointEndpoint = null;
|
|
1298
|
+
if (visiblePoints.pointCount < 2) {
|
|
1299
|
+
const singlePointSegment = getSinglePointSegment({
|
|
1300
|
+
series,
|
|
1301
|
+
state,
|
|
1302
|
+
yRange,
|
|
1303
|
+
plot
|
|
1304
|
+
});
|
|
1305
|
+
if (!singlePointSegment) return;
|
|
1306
|
+
visiblePoints = singlePointSegment;
|
|
1307
|
+
singlePointEndpoint = singlePointSegment.endpoint;
|
|
1308
|
+
}
|
|
1309
|
+
const animatedPoint = singlePointEndpoint ? null : getAppendAnimatedPoint({
|
|
1310
|
+
seriesId: series.id,
|
|
1311
|
+
visiblePoints,
|
|
1312
|
+
now
|
|
1313
|
+
});
|
|
1314
|
+
const pointCount = animatedPoint?.pointCount ?? visiblePoints.pointCount;
|
|
1315
|
+
if (animatedPoint) {
|
|
1316
|
+
seriesEndpoints.set(series.id, {
|
|
1317
|
+
x: animatedPoint.x,
|
|
1318
|
+
y: animatedPoint.y
|
|
1319
|
+
});
|
|
1320
|
+
} else if (singlePointEndpoint) {
|
|
1321
|
+
seriesEndpoints.set(series.id, singlePointEndpoint);
|
|
1322
|
+
}
|
|
1323
|
+
const [r, g, b] = hexToRgb(series.color);
|
|
1324
|
+
gl.uniform3f(activeLocations.uColor, r, g, b);
|
|
1325
|
+
if (useAntialias) {
|
|
1326
|
+
const requiredFloats = Math.max(0, pointCount - 1) * 6 * AA_VERTEX_FLOAT_STRIDE;
|
|
1327
|
+
const entry2 = getDynamicBuffer(
|
|
1328
|
+
gl,
|
|
1329
|
+
antialiasBuffers,
|
|
1330
|
+
series.id,
|
|
1331
|
+
requiredFloats
|
|
1332
|
+
);
|
|
1333
|
+
const drawVertexCount = fillAntialiasSeriesSegments({
|
|
1334
|
+
vertices: entry2.vertices,
|
|
1335
|
+
visiblePoints,
|
|
1336
|
+
pointCount,
|
|
1337
|
+
animatedPoint
|
|
1338
|
+
});
|
|
1339
|
+
const stride = AA_VERTEX_FLOAT_STRIDE * Float32Array.BYTES_PER_ELEMENT;
|
|
1340
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, entry2.buffer);
|
|
1341
|
+
gl.vertexAttribPointer(
|
|
1342
|
+
aaLocations.aStart,
|
|
1343
|
+
2,
|
|
1344
|
+
gl.FLOAT,
|
|
1345
|
+
false,
|
|
1346
|
+
stride,
|
|
1347
|
+
0
|
|
1348
|
+
);
|
|
1349
|
+
gl.vertexAttribPointer(
|
|
1350
|
+
aaLocations.aEnd,
|
|
1351
|
+
2,
|
|
1352
|
+
gl.FLOAT,
|
|
1353
|
+
false,
|
|
1354
|
+
stride,
|
|
1355
|
+
2 * Float32Array.BYTES_PER_ELEMENT
|
|
1356
|
+
);
|
|
1357
|
+
gl.vertexAttribPointer(
|
|
1358
|
+
aaLocations.aSide,
|
|
1359
|
+
1,
|
|
1360
|
+
gl.FLOAT,
|
|
1361
|
+
false,
|
|
1362
|
+
stride,
|
|
1363
|
+
4 * Float32Array.BYTES_PER_ELEMENT
|
|
1364
|
+
);
|
|
1365
|
+
gl.vertexAttribPointer(
|
|
1366
|
+
aaLocations.aAlong,
|
|
1367
|
+
1,
|
|
1368
|
+
gl.FLOAT,
|
|
1369
|
+
false,
|
|
1370
|
+
stride,
|
|
1371
|
+
5 * Float32Array.BYTES_PER_ELEMENT
|
|
1372
|
+
);
|
|
1373
|
+
gl.bufferSubData(
|
|
1374
|
+
gl.ARRAY_BUFFER,
|
|
1375
|
+
0,
|
|
1376
|
+
entry2.vertices,
|
|
1377
|
+
0,
|
|
1378
|
+
drawVertexCount * AA_VERTEX_FLOAT_STRIDE
|
|
1379
|
+
);
|
|
1380
|
+
gl.drawArrays(gl.TRIANGLES, 0, drawVertexCount);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
const entry = getDynamicBuffer(
|
|
1384
|
+
gl,
|
|
1385
|
+
buffers,
|
|
1386
|
+
series.id,
|
|
1387
|
+
pointCount * 2
|
|
1388
|
+
);
|
|
1389
|
+
fillSeriesPoints({
|
|
1390
|
+
vertices: entry.vertices,
|
|
1391
|
+
visiblePoints,
|
|
1392
|
+
pointCount,
|
|
1393
|
+
animatedPoint
|
|
1394
|
+
});
|
|
1395
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, entry.buffer);
|
|
1396
|
+
gl.vertexAttribPointer(locations.aXy, 2, gl.FLOAT, false, 0, 0);
|
|
1397
|
+
gl.bufferSubData(
|
|
1398
|
+
gl.ARRAY_BUFFER,
|
|
1399
|
+
0,
|
|
1400
|
+
entry.vertices,
|
|
1401
|
+
0,
|
|
1402
|
+
pointCount * 2
|
|
1403
|
+
);
|
|
1404
|
+
gl.drawArrays(gl.LINE_STRIP, 0, pointCount);
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
const movingSeries = getMovingAverageSeriesForChart({
|
|
1408
|
+
chart,
|
|
1409
|
+
movingAverage: descriptor.capabilities.movingAverage ? movingAverage : null,
|
|
1410
|
+
cache: movingAverageCache
|
|
1411
|
+
});
|
|
1412
|
+
if (movingSeries.length) {
|
|
1413
|
+
gl.disable(gl.BLEND);
|
|
1414
|
+
gl.useProgram(program);
|
|
1415
|
+
gl.enableVertexAttribArray(locations.aXy);
|
|
1416
|
+
setCommonUniforms(
|
|
1417
|
+
locations,
|
|
1418
|
+
pixelWidth,
|
|
1419
|
+
pixelHeight,
|
|
1420
|
+
scaledPlot,
|
|
1421
|
+
state,
|
|
1422
|
+
yRange
|
|
1423
|
+
);
|
|
1424
|
+
getOrderedSeries(
|
|
1425
|
+
{ ...chart, series: movingSeries },
|
|
1426
|
+
seriesOrderByChart
|
|
1427
|
+
).forEach((series) => {
|
|
1428
|
+
const visiblePoints = series.getVisiblePoints(
|
|
1429
|
+
state.xMin,
|
|
1430
|
+
state.xMax,
|
|
1431
|
+
plot.width
|
|
1432
|
+
);
|
|
1433
|
+
if (visiblePoints.pointCount < 2) return;
|
|
1434
|
+
const entry = getDynamicBuffer(
|
|
1435
|
+
gl,
|
|
1436
|
+
buffers,
|
|
1437
|
+
series.id,
|
|
1438
|
+
visiblePoints.pointCount * 8 * 2
|
|
1439
|
+
);
|
|
1440
|
+
const drawVertexCount = fillDashedSeriesSegments({
|
|
1441
|
+
vertices: entry.vertices,
|
|
1442
|
+
visiblePoints,
|
|
1443
|
+
pointCount: visiblePoints.pointCount,
|
|
1444
|
+
state,
|
|
1445
|
+
yRange,
|
|
1446
|
+
plot
|
|
1447
|
+
});
|
|
1448
|
+
if (drawVertexCount < 2) return;
|
|
1449
|
+
const [r, g, b] = hexToRgb(series.color);
|
|
1450
|
+
gl.uniform3f(locations.uColor, r, g, b);
|
|
1451
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, entry.buffer);
|
|
1452
|
+
gl.vertexAttribPointer(locations.aXy, 2, gl.FLOAT, false, 0, 0);
|
|
1453
|
+
gl.bufferSubData(
|
|
1454
|
+
gl.ARRAY_BUFFER,
|
|
1455
|
+
0,
|
|
1456
|
+
entry.vertices,
|
|
1457
|
+
0,
|
|
1458
|
+
drawVertexCount * 2
|
|
1459
|
+
);
|
|
1460
|
+
gl.drawArrays(gl.LINES, 0, drawVertexCount);
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
gl.disable(gl.BLEND);
|
|
1464
|
+
return { seriesEndpoints };
|
|
1465
|
+
},
|
|
1466
|
+
destroy() {
|
|
1467
|
+
deleteBufferCache(gl, buffers);
|
|
1468
|
+
deleteBufferCache(gl, antialiasBuffers);
|
|
1469
|
+
movingAverageCache.clear();
|
|
1470
|
+
gl.deleteProgram(program);
|
|
1471
|
+
gl.deleteProgram(antialiasProgram);
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
};
|
|
1475
|
+
|
|
1476
|
+
// src/core/renderers/index.js
|
|
1477
|
+
var RENDERER_FACTORIES = Object.freeze({
|
|
1478
|
+
bar: createBarRenderer,
|
|
1479
|
+
line: createLineRenderer
|
|
1480
|
+
});
|
|
1481
|
+
var createRendererRegistry = (gl) => {
|
|
1482
|
+
const renderers = new Map(
|
|
1483
|
+
Object.entries(RENDERER_FACTORIES).map(([type, createRenderer]) => [
|
|
1484
|
+
type,
|
|
1485
|
+
createRenderer(gl)
|
|
1486
|
+
])
|
|
1487
|
+
);
|
|
1488
|
+
return {
|
|
1489
|
+
draw(type, payload) {
|
|
1490
|
+
const renderer = renderers.get(type);
|
|
1491
|
+
if (!renderer) {
|
|
1492
|
+
throw new TypeError(`No AlienCharts renderer registered for "${type}"`);
|
|
1493
|
+
}
|
|
1494
|
+
return renderer.draw(payload);
|
|
1495
|
+
},
|
|
1496
|
+
getTooltipCategory(type, payload) {
|
|
1497
|
+
const renderer = renderers.get(type);
|
|
1498
|
+
if (!renderer) {
|
|
1499
|
+
throw new TypeError(`No AlienCharts renderer registered for "${type}"`);
|
|
1500
|
+
}
|
|
1501
|
+
return renderer.getTooltipCategory?.(payload) ?? payload.visiblePoints.x[payload.pointIndex];
|
|
1502
|
+
},
|
|
1503
|
+
destroy() {
|
|
1504
|
+
renderers.forEach((renderer) => renderer.destroy());
|
|
1505
|
+
renderers.clear();
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
};
|
|
1509
|
+
|
|
1510
|
+
// src/core/chartRenderer.js
|
|
1511
|
+
var CHART_HEIGHT = 360;
|
|
1512
|
+
var RIGHT_AXIS_WIDTH = 58;
|
|
1513
|
+
var PLOT_PADDING = Object.freeze({
|
|
1514
|
+
left: 38,
|
|
1515
|
+
right: RIGHT_AXIS_WIDTH,
|
|
1516
|
+
top: 34,
|
|
1517
|
+
bottom: 28
|
|
1518
|
+
});
|
|
1519
|
+
var HORIZONTAL_PLOT_PADDING = Object.freeze({
|
|
1520
|
+
left: RIGHT_AXIS_WIDTH,
|
|
1521
|
+
right: 20,
|
|
1522
|
+
top: 34,
|
|
1523
|
+
bottom: 28
|
|
1524
|
+
});
|
|
1525
|
+
var CATEGORICAL_PLOT_PADDING = Object.freeze({
|
|
1526
|
+
...PLOT_PADDING,
|
|
1527
|
+
bottom: 42
|
|
1528
|
+
});
|
|
1529
|
+
var CATEGORICAL_HORIZONTAL_PLOT_PADDING = Object.freeze({
|
|
1530
|
+
...HORIZONTAL_PLOT_PADDING,
|
|
1531
|
+
left: 128
|
|
1532
|
+
});
|
|
1533
|
+
var Y_SCALE_MIN = 0.05;
|
|
1534
|
+
var Y_SCALE_MAX = 40;
|
|
1535
|
+
var Y_AXIS_TICK_COUNT = 5;
|
|
1536
|
+
var X_AXIS_TICK_COUNT = 5;
|
|
1537
|
+
var X_SCALE_MIN_SPAN = 10;
|
|
1538
|
+
var X_SCALE_MAX_SPAN = 1e9;
|
|
1539
|
+
var JUMP_LATEST_RIGHT_PADDING_RATIO = 0.1;
|
|
1540
|
+
var DEFAULT_CHART_BACKGROUND = "#f5f9ff";
|
|
1541
|
+
var getChartOrientation = (chart, descriptor) => (descriptor || resolveChartDescriptor(chart)).orientation;
|
|
1542
|
+
var getPlotPadding = (chart, descriptor) => {
|
|
1543
|
+
const categorical = getChartCategories(chart).length > 0;
|
|
1544
|
+
if (getChartOrientation(chart, descriptor) === "horizontal") {
|
|
1545
|
+
return categorical ? CATEGORICAL_HORIZONTAL_PLOT_PADDING : HORIZONTAL_PLOT_PADDING;
|
|
1546
|
+
}
|
|
1547
|
+
return categorical ? CATEGORICAL_PLOT_PADDING : PLOT_PADDING;
|
|
1548
|
+
};
|
|
1549
|
+
var getCategoryPixelLength = (chart, plot, descriptor) => getChartOrientation(chart, descriptor) === "horizontal" ? plot.height : plot.width;
|
|
1550
|
+
var getReadableTextColor = (backgroundColor) => {
|
|
1551
|
+
const [r, g, b] = hexToRgb(backgroundColor);
|
|
1552
|
+
const toLinear = (value) => value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
1553
|
+
const luminance = 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
1554
|
+
return luminance > 0.45 ? "#111827" : "#ffffff";
|
|
1555
|
+
};
|
|
1556
|
+
var getSeriesLabelTextColor = (series) => {
|
|
1557
|
+
if (series.__labelTextColorFor !== series.color) {
|
|
1558
|
+
series.__labelTextColorFor = series.color;
|
|
1559
|
+
series.__labelTextColor = getReadableTextColor(series.color);
|
|
1560
|
+
}
|
|
1561
|
+
return series.__labelTextColor;
|
|
1562
|
+
};
|
|
1563
|
+
var lowerBound2 = (array, value) => {
|
|
1564
|
+
let low = 0;
|
|
1565
|
+
let high = array.length;
|
|
1566
|
+
while (low < high) {
|
|
1567
|
+
const middle = low + high >> 1;
|
|
1568
|
+
if (array[middle] < value) low = middle + 1;
|
|
1569
|
+
else high = middle;
|
|
1570
|
+
}
|
|
1571
|
+
return low;
|
|
1572
|
+
};
|
|
1573
|
+
var getNearestPointIndex = (xValues, xValue) => {
|
|
1574
|
+
if (!xValues.length) return -1;
|
|
1575
|
+
const nextIndex = lowerBound2(xValues, xValue);
|
|
1576
|
+
if (nextIndex <= 0) return 0;
|
|
1577
|
+
if (nextIndex >= xValues.length) return xValues.length - 1;
|
|
1578
|
+
const previousIndex = nextIndex - 1;
|
|
1579
|
+
return Math.abs(xValues[nextIndex] - xValue) < Math.abs(xValue - xValues[previousIndex]) ? nextIndex : previousIndex;
|
|
1580
|
+
};
|
|
1581
|
+
var getChartXBounds = (chart) => {
|
|
1582
|
+
let minX = Infinity;
|
|
1583
|
+
let maxX = -Infinity;
|
|
1584
|
+
chart.series.forEach((series) => {
|
|
1585
|
+
if (!series.length) return;
|
|
1586
|
+
minX = Math.min(minX, series.rawX[0]);
|
|
1587
|
+
maxX = Math.max(maxX, series.rawX[series.length - 1]);
|
|
1588
|
+
});
|
|
1589
|
+
return { minX, maxX };
|
|
1590
|
+
};
|
|
1591
|
+
var getMinimumCategorySpan = (chart) => {
|
|
1592
|
+
const categories = getChartCategories(chart).map((category) => category.value).sort((left, right) => left - right);
|
|
1593
|
+
if (!categories.length) return X_SCALE_MIN_SPAN;
|
|
1594
|
+
let minimumGap = Infinity;
|
|
1595
|
+
for (let index = 1; index < categories.length; index += 1) {
|
|
1596
|
+
minimumGap = Math.min(minimumGap, categories[index] - categories[index - 1]);
|
|
1597
|
+
}
|
|
1598
|
+
return Number.isFinite(minimumGap) ? minimumGap : 1;
|
|
886
1599
|
};
|
|
887
1600
|
var getInitialView = (chart, initialVisiblePoints = null) => {
|
|
1601
|
+
const categories = getChartCategories(chart).map((category) => category.value).sort((left, right) => left - right);
|
|
1602
|
+
if (categories.length) {
|
|
1603
|
+
const requestedCount = Number.isFinite(initialVisiblePoints) && initialVisiblePoints > 0 ? Math.max(1, Math.floor(initialVisiblePoints)) : categories.length;
|
|
1604
|
+
const visible = categories.slice(-requestedCount);
|
|
1605
|
+
const first = visible[0];
|
|
1606
|
+
const last = visible[visible.length - 1];
|
|
1607
|
+
const firstIndex = categories.indexOf(first);
|
|
1608
|
+
const lastIndex = categories.lastIndexOf(last);
|
|
1609
|
+
const leftGap = firstIndex > 0 ? first - categories[firstIndex - 1] : visible[1] - first || 1;
|
|
1610
|
+
const rightGap = lastIndex + 1 < categories.length ? categories[lastIndex + 1] - last : last - visible[visible.length - 2] || leftGap;
|
|
1611
|
+
return {
|
|
1612
|
+
xMin: first - leftGap / 2,
|
|
1613
|
+
xMax: last + rightGap / 2
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
888
1616
|
const { minX, maxX } = getChartXBounds(chart);
|
|
889
1617
|
if (!Number.isFinite(minX) || !Number.isFinite(maxX)) {
|
|
890
1618
|
return { xMin: 0, xMax: 1 };
|
|
891
1619
|
}
|
|
892
1620
|
if (minX === maxX) {
|
|
893
1621
|
const span2 = Number.isFinite(initialVisiblePoints) && initialVisiblePoints > 0 ? Math.max(10, initialVisiblePoints) : 10;
|
|
894
|
-
const
|
|
895
|
-
const nextMax = maxX + rightPadding2;
|
|
1622
|
+
const nextMax = maxX + span2 * JUMP_LATEST_RIGHT_PADDING_RATIO;
|
|
896
1623
|
return { xMin: nextMax - span2, xMax: nextMax };
|
|
897
1624
|
}
|
|
898
1625
|
let span = maxX - minX;
|
|
899
1626
|
if (Number.isFinite(initialVisiblePoints) && initialVisiblePoints > 0) {
|
|
900
1627
|
span = Math.min(span, initialVisiblePoints);
|
|
901
|
-
const
|
|
902
|
-
const nextMax = maxX + rightPadding2;
|
|
1628
|
+
const nextMax = maxX + span * JUMP_LATEST_RIGHT_PADDING_RATIO;
|
|
903
1629
|
return { xMin: nextMax - span, xMax: nextMax };
|
|
904
1630
|
}
|
|
905
|
-
|
|
906
|
-
|
|
1631
|
+
return {
|
|
1632
|
+
xMin: minX,
|
|
1633
|
+
xMax: maxX + span * JUMP_LATEST_RIGHT_PADDING_RATIO
|
|
1634
|
+
};
|
|
907
1635
|
};
|
|
908
|
-
var getYRange = (chart, xMin, xMax, width) => {
|
|
1636
|
+
var getYRange = (chart, xMin, xMax, width, suppliedDescriptor) => {
|
|
1637
|
+
const descriptor = suppliedDescriptor || resolveChartDescriptor(chart);
|
|
909
1638
|
let minY = Infinity;
|
|
910
1639
|
let maxY = -Infinity;
|
|
911
1640
|
let renderedPoints = 0;
|
|
@@ -914,26 +1643,53 @@ var getYRange = (chart, xMin, xMax, width) => {
|
|
|
914
1643
|
const visible = series.getVisiblePoints(xMin, xMax, width);
|
|
915
1644
|
renderedPoints += visible.pointCount;
|
|
916
1645
|
bucketSize = Math.max(bucketSize, visible.bucketSize);
|
|
917
|
-
for (let
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
if (value > maxY) maxY = value;
|
|
1646
|
+
for (let index = 0; index < visible.y.length; index += 1) {
|
|
1647
|
+
minY = Math.min(minY, visible.y[index]);
|
|
1648
|
+
maxY = Math.max(maxY, visible.y[index]);
|
|
921
1649
|
}
|
|
922
1650
|
});
|
|
923
|
-
if (!Number.isFinite(minY) || !Number.isFinite(maxY)) {
|
|
924
|
-
return { minY: 0, maxY: 1, renderedPoints, bucketSize };
|
|
925
|
-
}
|
|
926
1651
|
const fixedMinY = Number(chart.yRange?.min);
|
|
927
1652
|
const fixedMaxY = Number(chart.yRange?.max);
|
|
928
1653
|
if (Number.isFinite(fixedMinY) && Number.isFinite(fixedMaxY) && fixedMinY < fixedMaxY) {
|
|
929
|
-
return {
|
|
1654
|
+
return {
|
|
1655
|
+
minY: fixedMinY,
|
|
1656
|
+
maxY: fixedMaxY,
|
|
1657
|
+
renderedPoints,
|
|
1658
|
+
bucketSize
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
if (!Number.isFinite(minY) || !Number.isFinite(maxY)) {
|
|
1662
|
+
return { minY: 0, maxY: 1, renderedPoints, bucketSize };
|
|
1663
|
+
}
|
|
1664
|
+
if (descriptor.rangeIncludesZero) {
|
|
1665
|
+
if (minY >= 0) {
|
|
1666
|
+
return {
|
|
1667
|
+
minY: 0,
|
|
1668
|
+
maxY: maxY === 0 ? 1 : maxY * 1.08,
|
|
1669
|
+
renderedPoints,
|
|
1670
|
+
bucketSize
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
if (maxY <= 0) {
|
|
1674
|
+
return {
|
|
1675
|
+
minY: minY === 0 ? -1 : minY * 1.08,
|
|
1676
|
+
maxY: 0,
|
|
1677
|
+
renderedPoints,
|
|
1678
|
+
bucketSize
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
930
1681
|
}
|
|
931
1682
|
if (minY === maxY) {
|
|
932
1683
|
minY -= 1;
|
|
933
1684
|
maxY += 1;
|
|
934
1685
|
}
|
|
935
|
-
const
|
|
936
|
-
return {
|
|
1686
|
+
const padding = (maxY - minY) * 0.08;
|
|
1687
|
+
return {
|
|
1688
|
+
minY: minY - padding,
|
|
1689
|
+
maxY: maxY + padding,
|
|
1690
|
+
renderedPoints,
|
|
1691
|
+
bucketSize
|
|
1692
|
+
};
|
|
937
1693
|
};
|
|
938
1694
|
var applyYScale = (yRange, scale = 1, centerOffset = 0) => {
|
|
939
1695
|
const clampedScale = Math.min(Y_SCALE_MAX, Math.max(Y_SCALE_MIN, scale));
|
|
@@ -948,20 +1704,30 @@ var applyYScale = (yRange, scale = 1, centerOffset = 0) => {
|
|
|
948
1704
|
var trimFormattedNumber = (value) => value.replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
|
|
949
1705
|
var formatNumber = (value) => {
|
|
950
1706
|
if (!Number.isFinite(value)) return "";
|
|
951
|
-
const
|
|
952
|
-
if (
|
|
953
|
-
if (
|
|
954
|
-
if (
|
|
955
|
-
if (
|
|
956
|
-
if (
|
|
1707
|
+
const absolute = Math.abs(value);
|
|
1708
|
+
if (absolute !== 0 && absolute < 1e-4) return value.toExponential(2);
|
|
1709
|
+
if (absolute < 1) return trimFormattedNumber(value.toPrecision(5));
|
|
1710
|
+
if (absolute >= 1e5) return value.toFixed(0);
|
|
1711
|
+
if (absolute >= 1e3) return trimFormattedNumber(value.toFixed(1));
|
|
1712
|
+
if (absolute >= 10) return trimFormattedNumber(value.toFixed(2));
|
|
957
1713
|
return trimFormattedNumber(value.toFixed(4));
|
|
958
1714
|
};
|
|
959
1715
|
var formatCompactNumber = (value) => {
|
|
960
1716
|
if (!Number.isFinite(value)) return "";
|
|
961
|
-
const
|
|
962
|
-
if (
|
|
963
|
-
|
|
964
|
-
|
|
1717
|
+
const absolute = Math.abs(value);
|
|
1718
|
+
if (absolute >= 1e9) {
|
|
1719
|
+
return `${(value / 1e9).toFixed(
|
|
1720
|
+
absolute >= 1e10 ? 0 : 1
|
|
1721
|
+
)}B`;
|
|
1722
|
+
}
|
|
1723
|
+
if (absolute >= 1e6) {
|
|
1724
|
+
return `${(value / 1e6).toFixed(
|
|
1725
|
+
absolute >= 1e7 ? 0 : 1
|
|
1726
|
+
)}M`;
|
|
1727
|
+
}
|
|
1728
|
+
if (absolute >= 1e3) {
|
|
1729
|
+
return `${(value / 1e3).toFixed(absolute >= 1e4 ? 0 : 1)}k`;
|
|
1730
|
+
}
|
|
965
1731
|
return formatNumber(value);
|
|
966
1732
|
};
|
|
967
1733
|
var normalizeRect = (start, end) => {
|
|
@@ -979,8 +1745,27 @@ var clampPointToPlot = (point, plot) => ({
|
|
|
979
1745
|
x: Math.min(plot.x + plot.width, Math.max(plot.x, point.x)),
|
|
980
1746
|
y: Math.min(plot.y + plot.height, Math.max(plot.y, point.y))
|
|
981
1747
|
});
|
|
1748
|
+
var getScaledYRangeForLayout = ({
|
|
1749
|
+
chart,
|
|
1750
|
+
descriptor,
|
|
1751
|
+
state,
|
|
1752
|
+
plot,
|
|
1753
|
+
yScaleRef,
|
|
1754
|
+
yCenterOffsetRef
|
|
1755
|
+
}) => applyYScale(
|
|
1756
|
+
getYRange(
|
|
1757
|
+
chart,
|
|
1758
|
+
state.xMin,
|
|
1759
|
+
state.xMax,
|
|
1760
|
+
getCategoryPixelLength(chart, plot, descriptor),
|
|
1761
|
+
descriptor
|
|
1762
|
+
),
|
|
1763
|
+
yScaleRef.current.get(chart.id) ?? 1,
|
|
1764
|
+
yCenterOffsetRef.current.get(chart.id) ?? 0
|
|
1765
|
+
);
|
|
982
1766
|
var applyRectangleZoom = ({
|
|
983
1767
|
chart,
|
|
1768
|
+
descriptor: suppliedDescriptor,
|
|
984
1769
|
plot,
|
|
985
1770
|
start,
|
|
986
1771
|
end,
|
|
@@ -990,120 +1775,164 @@ var applyRectangleZoom = ({
|
|
|
990
1775
|
yCenterOffsetRef,
|
|
991
1776
|
yManualScaleRef
|
|
992
1777
|
}) => {
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
|
|
1778
|
+
const rect = normalizeRect(
|
|
1779
|
+
clampPointToPlot(start, plot),
|
|
1780
|
+
clampPointToPlot(end, plot)
|
|
1781
|
+
);
|
|
996
1782
|
if (!rect || rect.width < 6 || rect.height < 6) return false;
|
|
1783
|
+
const descriptor = suppliedDescriptor || resolveChartDescriptor(chart);
|
|
997
1784
|
const state = viewStateRef.current.get(chart.id) || getInitialView(chart, initialVisiblePoints);
|
|
998
|
-
const currentYRange =
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1785
|
+
const currentYRange = getScaledYRangeForLayout({
|
|
1786
|
+
chart,
|
|
1787
|
+
descriptor,
|
|
1788
|
+
state,
|
|
1789
|
+
plot,
|
|
1790
|
+
yScaleRef,
|
|
1791
|
+
yCenterOffsetRef
|
|
1792
|
+
});
|
|
1793
|
+
const transform = createCoordinateTransform({
|
|
1794
|
+
orientation: descriptor.orientation,
|
|
1795
|
+
categoryRange: { min: state.xMin, max: state.xMax },
|
|
1796
|
+
valueRange: {
|
|
1797
|
+
min: currentYRange.minY,
|
|
1798
|
+
max: currentYRange.maxY
|
|
1799
|
+
},
|
|
1800
|
+
plot
|
|
1801
|
+
});
|
|
1802
|
+
const selected = transform.dataBoundsForRect(rect);
|
|
1803
|
+
if (selected.categoryMax - selected.categoryMin < getMinimumCategorySpan(chart)) {
|
|
1804
|
+
return false;
|
|
1805
|
+
}
|
|
1806
|
+
const selectedSpan = selected.valueMax - selected.valueMin;
|
|
1013
1807
|
if (!Number.isFinite(selectedSpan) || selectedSpan <= 0) return false;
|
|
1014
|
-
viewStateRef.current.set(chart.id, {
|
|
1015
|
-
|
|
1808
|
+
viewStateRef.current.set(chart.id, {
|
|
1809
|
+
xMin: selected.categoryMin,
|
|
1810
|
+
xMax: selected.categoryMax
|
|
1811
|
+
});
|
|
1812
|
+
const baseYRange = getYRange(
|
|
1813
|
+
chart,
|
|
1814
|
+
selected.categoryMin,
|
|
1815
|
+
selected.categoryMax,
|
|
1816
|
+
getCategoryPixelLength(chart, plot, descriptor),
|
|
1817
|
+
descriptor
|
|
1818
|
+
);
|
|
1016
1819
|
const baseSpan = baseYRange.maxY - baseYRange.minY;
|
|
1017
1820
|
if (Number.isFinite(baseSpan) && baseSpan > 0) {
|
|
1018
|
-
const selectedCenter = (
|
|
1821
|
+
const selectedCenter = (selected.valueMin + selected.valueMax) / 2;
|
|
1019
1822
|
const baseCenter = (baseYRange.minY + baseYRange.maxY) / 2;
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
Math.
|
|
1823
|
+
yScaleRef.current.set(
|
|
1824
|
+
chart.id,
|
|
1825
|
+
Math.min(
|
|
1826
|
+
Y_SCALE_MAX,
|
|
1827
|
+
Math.max(Y_SCALE_MIN, selectedSpan / baseSpan)
|
|
1828
|
+
)
|
|
1023
1829
|
);
|
|
1024
|
-
yScaleRef.current.set(chart.id, nextScale);
|
|
1025
1830
|
yCenterOffsetRef.current.set(chart.id, selectedCenter - baseCenter);
|
|
1026
1831
|
yManualScaleRef.current.add(chart.id);
|
|
1027
1832
|
}
|
|
1028
1833
|
return true;
|
|
1029
1834
|
};
|
|
1030
|
-
var getChartState = (chart, initialVisiblePoints, viewStateRef) => viewStateRef.current.get(chart.id) || getInitialView(chart, initialVisiblePoints);
|
|
1031
|
-
var getScaledYRangeForLayout = ({
|
|
1032
|
-
chart,
|
|
1033
|
-
state,
|
|
1034
|
-
plot,
|
|
1035
|
-
yScaleRef,
|
|
1036
|
-
yCenterOffsetRef
|
|
1037
|
-
}) => applyYScale(
|
|
1038
|
-
getYRange(chart, state.xMin, state.xMax, plot.width),
|
|
1039
|
-
yScaleRef.current.get(chart.id) ?? 1,
|
|
1040
|
-
yCenterOffsetRef.current.get(chart.id) ?? 0
|
|
1041
|
-
);
|
|
1042
1835
|
var screenPointToDataPoint = ({
|
|
1043
1836
|
point,
|
|
1044
1837
|
chart,
|
|
1838
|
+
descriptor: suppliedDescriptor,
|
|
1045
1839
|
plot,
|
|
1046
1840
|
initialVisiblePoints,
|
|
1047
1841
|
viewStateRef,
|
|
1048
1842
|
yScaleRef,
|
|
1049
1843
|
yCenterOffsetRef
|
|
1050
1844
|
}) => {
|
|
1051
|
-
const
|
|
1845
|
+
const descriptor = suppliedDescriptor || resolveChartDescriptor(chart);
|
|
1846
|
+
const state = viewStateRef.current.get(chart.id) || getInitialView(chart, initialVisiblePoints);
|
|
1052
1847
|
const yRange = getScaledYRangeForLayout({
|
|
1053
1848
|
chart,
|
|
1849
|
+
descriptor,
|
|
1054
1850
|
state,
|
|
1055
1851
|
plot,
|
|
1056
1852
|
yScaleRef,
|
|
1057
1853
|
yCenterOffsetRef
|
|
1058
1854
|
});
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
};
|
|
1065
|
-
};
|
|
1066
|
-
var dataPointToScreenPoint = ({ point, state, yRange, plot }) => {
|
|
1067
|
-
const xRatio = (point.x - state.xMin) / (state.xMax - state.xMin);
|
|
1068
|
-
const yRatio = (yRange.maxY - point.y) / (yRange.maxY - yRange.minY);
|
|
1069
|
-
return {
|
|
1070
|
-
x: plot.x + xRatio * plot.width,
|
|
1071
|
-
y: plot.y + yRatio * plot.height
|
|
1072
|
-
};
|
|
1855
|
+
return createCoordinateTransform({
|
|
1856
|
+
orientation: descriptor.orientation,
|
|
1857
|
+
categoryRange: { min: state.xMin, max: state.xMax },
|
|
1858
|
+
valueRange: { min: yRange.minY, max: yRange.maxY },
|
|
1859
|
+
plot
|
|
1860
|
+
}).screenToData(point);
|
|
1073
1861
|
};
|
|
1074
1862
|
var createAxisOverlay = ({
|
|
1075
1863
|
chart,
|
|
1864
|
+
descriptor,
|
|
1076
1865
|
plot,
|
|
1077
1866
|
state,
|
|
1078
1867
|
yRange,
|
|
1079
1868
|
seriesEndpoints,
|
|
1080
|
-
seriesOrderByChart
|
|
1869
|
+
seriesOrderByChart
|
|
1081
1870
|
}) => {
|
|
1082
|
-
const
|
|
1871
|
+
const horizontal = descriptor.orientation === "horizontal";
|
|
1872
|
+
const transform = createCoordinateTransform({
|
|
1873
|
+
orientation: descriptor.orientation,
|
|
1874
|
+
categoryRange: { min: state.xMin, max: state.xMax },
|
|
1875
|
+
valueRange: { min: yRange.minY, max: yRange.maxY },
|
|
1876
|
+
plot: { x: 0, y: 0, width: plot.width, height: plot.height }
|
|
1877
|
+
});
|
|
1083
1878
|
const ticks = Array.from({ length: Y_AXIS_TICK_COUNT }, (_, index) => {
|
|
1084
|
-
const ratio =
|
|
1879
|
+
const ratio = index / (Y_AXIS_TICK_COUNT - 1);
|
|
1085
1880
|
const value = yRange.maxY - ratio * (yRange.maxY - yRange.minY);
|
|
1881
|
+
const screen = transform.dataToScreen({
|
|
1882
|
+
x: state.xMin,
|
|
1883
|
+
y: value
|
|
1884
|
+
});
|
|
1086
1885
|
return {
|
|
1087
1886
|
id: `${chart.id}-tick-${index}`,
|
|
1088
1887
|
value,
|
|
1089
|
-
top:
|
|
1888
|
+
top: horizontal ? void 0 : screen.y,
|
|
1889
|
+
left: horizontal ? screen.x : void 0
|
|
1090
1890
|
};
|
|
1091
1891
|
});
|
|
1092
|
-
const
|
|
1093
|
-
|
|
1892
|
+
const categories = getChartCategories(chart);
|
|
1893
|
+
const visibleCategories = categories.filter(
|
|
1894
|
+
(category) => category.value >= state.xMin && category.value <= state.xMax
|
|
1895
|
+
).sort((left, right) => left.value - right.value);
|
|
1896
|
+
const categoryPixelLength = horizontal ? plot.height : plot.width;
|
|
1897
|
+
const maximumCategoryLabels = Math.max(
|
|
1898
|
+
1,
|
|
1899
|
+
Math.floor(categoryPixelLength / (horizontal ? 22 : 72))
|
|
1900
|
+
);
|
|
1901
|
+
const categoryLabelStride = Math.max(
|
|
1902
|
+
1,
|
|
1903
|
+
Math.ceil(visibleCategories.length / maximumCategoryLabels)
|
|
1904
|
+
);
|
|
1905
|
+
const displayedCategories = visibleCategories.filter(
|
|
1906
|
+
(_, index) => index % categoryLabelStride === 0 || index === visibleCategories.length - 1
|
|
1907
|
+
);
|
|
1908
|
+
const xTicks = categories.length ? displayedCategories.map((category, index) => {
|
|
1909
|
+
const screen = transform.dataToScreen({
|
|
1910
|
+
x: category.value,
|
|
1911
|
+
y: yRange.minY
|
|
1912
|
+
});
|
|
1913
|
+
return {
|
|
1914
|
+
id: `${chart.id}-category-${index}-${category.value}`,
|
|
1915
|
+
categorical: true,
|
|
1916
|
+
label: category.label,
|
|
1917
|
+
value: category.value,
|
|
1918
|
+
left: horizontal ? void 0 : screen.x,
|
|
1919
|
+
top: horizontal ? screen.y : void 0
|
|
1920
|
+
};
|
|
1921
|
+
}) : Array.from({ length: X_AXIS_TICK_COUNT }, (_, index) => {
|
|
1922
|
+
const ratio = index / (X_AXIS_TICK_COUNT - 1);
|
|
1094
1923
|
return {
|
|
1095
1924
|
id: `${chart.id}-x-tick-${index}`,
|
|
1096
1925
|
value: state.xMin + ratio * (state.xMax - state.xMin),
|
|
1097
|
-
left: 18 + ratio * Math.max(1, plot.width - 36)
|
|
1926
|
+
left: horizontal ? void 0 : 18 + ratio * Math.max(1, plot.width - 36),
|
|
1927
|
+
top: horizontal ? 10 + ratio * Math.max(1, plot.height - 20) : void 0
|
|
1098
1928
|
};
|
|
1099
1929
|
});
|
|
1100
|
-
const latestValues = getOrderedSeries(chart, seriesOrderByChart).map((series) => {
|
|
1101
|
-
if (series.length
|
|
1930
|
+
const latestValues = descriptor.capabilities.latestValue ? getOrderedSeries(chart, seriesOrderByChart).map((series) => {
|
|
1931
|
+
if (!series.length) return null;
|
|
1102
1932
|
const value = series.rawY[series.length - 1];
|
|
1103
1933
|
const x = series.rawX[series.length - 1];
|
|
1104
1934
|
const endpoint = seriesEndpoints.get(series.id) || { x, y: value };
|
|
1105
|
-
const
|
|
1106
|
-
const xRatio = (endpoint.x - state.xMin) / (state.xMax - state.xMin);
|
|
1935
|
+
const screen = transform.dataToScreen(endpoint);
|
|
1107
1936
|
return {
|
|
1108
1937
|
id: series.id,
|
|
1109
1938
|
color: series.color,
|
|
@@ -1111,13 +1940,15 @@ var createAxisOverlay = ({
|
|
|
1111
1940
|
value: endpoint.y,
|
|
1112
1941
|
rawValue: value,
|
|
1113
1942
|
x,
|
|
1114
|
-
left:
|
|
1115
|
-
top:
|
|
1943
|
+
left: screen.x,
|
|
1944
|
+
top: screen.y
|
|
1116
1945
|
};
|
|
1117
1946
|
}).filter(
|
|
1118
|
-
(
|
|
1119
|
-
);
|
|
1947
|
+
(item) => item && Number.isFinite(item.top) && item.top >= -10 && item.top <= plot.height + 10
|
|
1948
|
+
) : [];
|
|
1949
|
+
const { maxX } = getChartXBounds(chart);
|
|
1120
1950
|
return {
|
|
1951
|
+
orientation: descriptor.orientation,
|
|
1121
1952
|
ticks,
|
|
1122
1953
|
xTicks,
|
|
1123
1954
|
latestValues,
|
|
@@ -1130,20 +1961,16 @@ var drawChartLayouts = ({
|
|
|
1130
1961
|
width,
|
|
1131
1962
|
height,
|
|
1132
1963
|
gl,
|
|
1133
|
-
|
|
1134
|
-
antialiasProgram,
|
|
1964
|
+
rendererRegistry,
|
|
1135
1965
|
antialiasLines = false,
|
|
1136
1966
|
layouts,
|
|
1137
1967
|
viewStateRef,
|
|
1138
1968
|
yScaleRef,
|
|
1139
1969
|
yCenterOffsetRef,
|
|
1140
|
-
seriesBufferCache,
|
|
1141
|
-
antialiasSeriesBufferCache,
|
|
1142
1970
|
initialVisiblePoints,
|
|
1143
1971
|
getAppendAnimatedPoint,
|
|
1144
|
-
movingAverageByChart
|
|
1145
|
-
|
|
1146
|
-
seriesOrderByChart = EMPTY_OBJECT
|
|
1972
|
+
movingAverageByChart,
|
|
1973
|
+
seriesOrderByChart
|
|
1147
1974
|
}) => {
|
|
1148
1975
|
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
|
1149
1976
|
const pixelWidth = Math.max(1, Math.floor(width * dpr));
|
|
@@ -1157,50 +1984,12 @@ var drawChartLayouts = ({
|
|
|
1157
1984
|
gl.viewport(0, 0, pixelWidth, pixelHeight);
|
|
1158
1985
|
gl.clearColor(0, 0, 0, 0);
|
|
1159
1986
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
1160
|
-
const activeAntialiasLines = antialiasLines && antialiasProgram;
|
|
1161
|
-
const activeProgram = activeAntialiasLines ? antialiasProgram : program;
|
|
1162
|
-
gl.useProgram(activeProgram);
|
|
1163
|
-
const uResolution = gl.getUniformLocation(activeProgram, "u_resolution");
|
|
1164
|
-
const uRect = gl.getUniformLocation(activeProgram, "u_rect");
|
|
1165
|
-
const uXRange = gl.getUniformLocation(activeProgram, "u_xRange");
|
|
1166
|
-
const uYRange = gl.getUniformLocation(activeProgram, "u_yRange");
|
|
1167
|
-
const uColor = gl.getUniformLocation(activeProgram, "u_color");
|
|
1168
|
-
let aXy = -1;
|
|
1169
|
-
let aStart = -1;
|
|
1170
|
-
let aEnd = -1;
|
|
1171
|
-
let aSide = -1;
|
|
1172
|
-
let aAlong = -1;
|
|
1173
|
-
let uLineHalfWidth = null;
|
|
1174
|
-
let uEdgeWidth = null;
|
|
1175
|
-
if (activeAntialiasLines) {
|
|
1176
|
-
aStart = gl.getAttribLocation(activeProgram, "a_start");
|
|
1177
|
-
aEnd = gl.getAttribLocation(activeProgram, "a_end");
|
|
1178
|
-
aSide = gl.getAttribLocation(activeProgram, "a_side");
|
|
1179
|
-
aAlong = gl.getAttribLocation(activeProgram, "a_along");
|
|
1180
|
-
uLineHalfWidth = gl.getUniformLocation(activeProgram, "u_lineHalfWidth");
|
|
1181
|
-
uEdgeWidth = gl.getUniformLocation(activeProgram, "u_edgeWidth");
|
|
1182
|
-
gl.enableVertexAttribArray(aStart);
|
|
1183
|
-
gl.enableVertexAttribArray(aEnd);
|
|
1184
|
-
gl.enableVertexAttribArray(aSide);
|
|
1185
|
-
gl.enableVertexAttribArray(aAlong);
|
|
1186
|
-
gl.uniform1f(uLineHalfWidth, AA_LINE_WIDTH_PX * dpr / 2);
|
|
1187
|
-
gl.uniform1f(uEdgeWidth, AA_EDGE_WIDTH_PX * dpr);
|
|
1188
|
-
gl.enable(gl.BLEND);
|
|
1189
|
-
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
1190
|
-
} else {
|
|
1191
|
-
aXy = gl.getAttribLocation(activeProgram, "a_xy");
|
|
1192
|
-
gl.enableVertexAttribArray(aXy);
|
|
1193
|
-
gl.disable(gl.BLEND);
|
|
1194
|
-
}
|
|
1195
|
-
gl.uniform2f(uResolution, pixelWidth, pixelHeight);
|
|
1196
1987
|
const now = performance.now();
|
|
1197
1988
|
const nextAxisOverlays = {};
|
|
1198
|
-
layouts.forEach((
|
|
1199
|
-
if (!visible) return;
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
gl.uniform2f(uResolution, pixelWidth, pixelHeight);
|
|
1203
|
-
}
|
|
1989
|
+
layouts.forEach((layout) => {
|
|
1990
|
+
if (!layout.visible) return;
|
|
1991
|
+
const { chart, plot } = layout;
|
|
1992
|
+
const descriptor = layout.descriptor || getChartDescriptor(chart);
|
|
1204
1993
|
const state = viewStateRef.current.get(chart.id) || getInitialView(chart, initialVisiblePoints);
|
|
1205
1994
|
const scaledPlot = {
|
|
1206
1995
|
x: plot.x * dpr,
|
|
@@ -1208,14 +1997,22 @@ var drawChartLayouts = ({
|
|
|
1208
1997
|
width: plot.width * dpr,
|
|
1209
1998
|
height: plot.height * dpr
|
|
1210
1999
|
};
|
|
2000
|
+
const categoryPixelLength = getCategoryPixelLength(
|
|
2001
|
+
chart,
|
|
2002
|
+
plot,
|
|
2003
|
+
descriptor
|
|
2004
|
+
);
|
|
1211
2005
|
const yRange = applyYScale(
|
|
1212
|
-
getYRange(
|
|
2006
|
+
getYRange(
|
|
2007
|
+
chart,
|
|
2008
|
+
state.xMin,
|
|
2009
|
+
state.xMax,
|
|
2010
|
+
categoryPixelLength,
|
|
2011
|
+
descriptor
|
|
2012
|
+
),
|
|
1213
2013
|
yScaleRef.current.get(chart.id) ?? 1,
|
|
1214
2014
|
yCenterOffsetRef.current.get(chart.id) ?? 0
|
|
1215
2015
|
);
|
|
1216
|
-
const seriesEndpoints = /* @__PURE__ */ new Map();
|
|
1217
|
-
const chartMovingAverage = movingAverageByChart?.[chart.id];
|
|
1218
|
-
const hideBaseSeries = Boolean(chartMovingAverage?.enabled) && Boolean(chartMovingAverage?.hideBase);
|
|
1219
2016
|
gl.enable(gl.SCISSOR_TEST);
|
|
1220
2017
|
gl.scissor(
|
|
1221
2018
|
Math.floor(scaledPlot.x),
|
|
@@ -1223,208 +2020,42 @@ var drawChartLayouts = ({
|
|
|
1223
2020
|
Math.ceil(scaledPlot.width),
|
|
1224
2021
|
Math.ceil(scaledPlot.height)
|
|
1225
2022
|
);
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
scaledPlot.y,
|
|
1230
|
-
scaledPlot.width,
|
|
1231
|
-
scaledPlot.height
|
|
1232
|
-
);
|
|
1233
|
-
gl.uniform2f(uXRange, state.xMin, state.xMax);
|
|
1234
|
-
gl.uniform2f(uYRange, yRange.minY, yRange.maxY);
|
|
1235
|
-
if (!hideBaseSeries) getOrderedSeries(chart, seriesOrderByChart).forEach((series) => {
|
|
1236
|
-
let visiblePoints = series.getVisiblePoints(
|
|
1237
|
-
state.xMin,
|
|
1238
|
-
state.xMax,
|
|
1239
|
-
plot.width
|
|
1240
|
-
);
|
|
1241
|
-
let singlePointEndpoint = null;
|
|
1242
|
-
if (visiblePoints.pointCount < 2) {
|
|
1243
|
-
const singlePointSegment = getSinglePointSegment({
|
|
1244
|
-
series,
|
|
1245
|
-
state,
|
|
1246
|
-
yRange,
|
|
1247
|
-
plot
|
|
1248
|
-
});
|
|
1249
|
-
if (!singlePointSegment) return;
|
|
1250
|
-
visiblePoints = singlePointSegment;
|
|
1251
|
-
singlePointEndpoint = singlePointSegment.endpoint;
|
|
1252
|
-
}
|
|
1253
|
-
const animatedPoint = singlePointEndpoint ? null : getAppendAnimatedPoint({
|
|
1254
|
-
seriesId: series.id,
|
|
1255
|
-
visiblePoints,
|
|
1256
|
-
now
|
|
1257
|
-
});
|
|
1258
|
-
const pointCount = animatedPoint?.pointCount ?? visiblePoints.pointCount;
|
|
1259
|
-
if (animatedPoint) {
|
|
1260
|
-
seriesEndpoints.set(series.id, {
|
|
1261
|
-
x: animatedPoint.x,
|
|
1262
|
-
y: animatedPoint.y
|
|
1263
|
-
});
|
|
1264
|
-
} else if (singlePointEndpoint) {
|
|
1265
|
-
seriesEndpoints.set(series.id, singlePointEndpoint);
|
|
1266
|
-
}
|
|
1267
|
-
const [r, g, b] = hexToRgb(series.color);
|
|
1268
|
-
gl.uniform3f(uColor, r, g, b);
|
|
1269
|
-
if (activeAntialiasLines) {
|
|
1270
|
-
const bufferEntry2 = getAntialiasSeriesBuffer(
|
|
1271
|
-
gl,
|
|
1272
|
-
antialiasSeriesBufferCache,
|
|
1273
|
-
series.id,
|
|
1274
|
-
pointCount
|
|
1275
|
-
);
|
|
1276
|
-
const drawVertexCount = fillAntialiasSeriesSegments({
|
|
1277
|
-
vertices: bufferEntry2.vertices,
|
|
1278
|
-
visiblePoints,
|
|
1279
|
-
pointCount,
|
|
1280
|
-
animatedPoint
|
|
1281
|
-
});
|
|
1282
|
-
const stride = AA_VERTEX_FLOAT_STRIDE * Float32Array.BYTES_PER_ELEMENT;
|
|
1283
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, bufferEntry2.buffer);
|
|
1284
|
-
gl.vertexAttribPointer(aStart, 2, gl.FLOAT, false, stride, 0);
|
|
1285
|
-
gl.vertexAttribPointer(
|
|
1286
|
-
aEnd,
|
|
1287
|
-
2,
|
|
1288
|
-
gl.FLOAT,
|
|
1289
|
-
false,
|
|
1290
|
-
stride,
|
|
1291
|
-
2 * Float32Array.BYTES_PER_ELEMENT
|
|
1292
|
-
);
|
|
1293
|
-
gl.vertexAttribPointer(
|
|
1294
|
-
aSide,
|
|
1295
|
-
1,
|
|
1296
|
-
gl.FLOAT,
|
|
1297
|
-
false,
|
|
1298
|
-
stride,
|
|
1299
|
-
4 * Float32Array.BYTES_PER_ELEMENT
|
|
1300
|
-
);
|
|
1301
|
-
gl.vertexAttribPointer(
|
|
1302
|
-
aAlong,
|
|
1303
|
-
1,
|
|
1304
|
-
gl.FLOAT,
|
|
1305
|
-
false,
|
|
1306
|
-
stride,
|
|
1307
|
-
5 * Float32Array.BYTES_PER_ELEMENT
|
|
1308
|
-
);
|
|
1309
|
-
gl.bufferSubData(
|
|
1310
|
-
gl.ARRAY_BUFFER,
|
|
1311
|
-
0,
|
|
1312
|
-
bufferEntry2.vertices,
|
|
1313
|
-
0,
|
|
1314
|
-
drawVertexCount * AA_VERTEX_FLOAT_STRIDE
|
|
1315
|
-
);
|
|
1316
|
-
gl.drawArrays(gl.TRIANGLES, 0, drawVertexCount);
|
|
1317
|
-
return;
|
|
1318
|
-
}
|
|
1319
|
-
const bufferEntry = getSeriesBuffer(
|
|
1320
|
-
gl,
|
|
1321
|
-
seriesBufferCache,
|
|
1322
|
-
series.id,
|
|
1323
|
-
pointCount
|
|
1324
|
-
);
|
|
1325
|
-
fillSeriesPoints({
|
|
1326
|
-
vertices: bufferEntry.vertices,
|
|
1327
|
-
visiblePoints,
|
|
1328
|
-
pointCount,
|
|
1329
|
-
animatedPoint
|
|
1330
|
-
});
|
|
1331
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, bufferEntry.buffer);
|
|
1332
|
-
gl.vertexAttribPointer(aXy, 2, gl.FLOAT, false, 0, 0);
|
|
1333
|
-
gl.bufferSubData(
|
|
1334
|
-
gl.ARRAY_BUFFER,
|
|
1335
|
-
0,
|
|
1336
|
-
bufferEntry.vertices,
|
|
1337
|
-
0,
|
|
1338
|
-
pointCount * 2
|
|
1339
|
-
);
|
|
1340
|
-
gl.drawArrays(gl.LINE_STRIP, 0, pointCount);
|
|
1341
|
-
});
|
|
1342
|
-
const movingAverageSeries = getMovingAverageSeriesForChart({
|
|
2023
|
+
const result = rendererRegistry.draw(descriptor.rendererType, {
|
|
2024
|
+
antialiasLines,
|
|
2025
|
+
categoryPixelLength,
|
|
1343
2026
|
chart,
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
const maURect = gl.getUniformLocation(program, "u_rect");
|
|
1357
|
-
const maUXRange = gl.getUniformLocation(program, "u_xRange");
|
|
1358
|
-
const maUYRange = gl.getUniformLocation(program, "u_yRange");
|
|
1359
|
-
maUColor = gl.getUniformLocation(program, "u_color");
|
|
1360
|
-
maAXy = gl.getAttribLocation(program, "a_xy");
|
|
1361
|
-
gl.enableVertexAttribArray(maAXy);
|
|
1362
|
-
gl.uniform2f(maUResolution, pixelWidth, pixelHeight);
|
|
1363
|
-
gl.uniform4f(
|
|
1364
|
-
maURect,
|
|
1365
|
-
scaledPlot.x,
|
|
1366
|
-
scaledPlot.y,
|
|
1367
|
-
scaledPlot.width,
|
|
1368
|
-
scaledPlot.height
|
|
1369
|
-
);
|
|
1370
|
-
gl.uniform2f(maUXRange, state.xMin, state.xMax);
|
|
1371
|
-
gl.uniform2f(maUYRange, yRange.minY, yRange.maxY);
|
|
1372
|
-
}
|
|
1373
|
-
orderedMovingAverageSeries.forEach((series) => {
|
|
1374
|
-
const visiblePoints = series.getVisiblePoints(
|
|
1375
|
-
state.xMin,
|
|
1376
|
-
state.xMax,
|
|
1377
|
-
plot.width
|
|
1378
|
-
);
|
|
1379
|
-
if (visiblePoints.pointCount < 2) return;
|
|
1380
|
-
const pointCount = visiblePoints.pointCount;
|
|
1381
|
-
const [r, g, b] = hexToRgb(series.color);
|
|
1382
|
-
gl.uniform3f(maUColor, r, g, b);
|
|
1383
|
-
const bufferEntry = getSeriesBuffer(
|
|
1384
|
-
gl,
|
|
1385
|
-
seriesBufferCache,
|
|
1386
|
-
series.id,
|
|
1387
|
-
pointCount * 8
|
|
1388
|
-
);
|
|
1389
|
-
const drawVertexCount = fillDashedSeriesSegments({
|
|
1390
|
-
vertices: bufferEntry.vertices,
|
|
1391
|
-
visiblePoints,
|
|
1392
|
-
pointCount,
|
|
1393
|
-
state,
|
|
1394
|
-
yRange,
|
|
1395
|
-
plot
|
|
1396
|
-
});
|
|
1397
|
-
if (drawVertexCount < 2) return;
|
|
1398
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, bufferEntry.buffer);
|
|
1399
|
-
gl.vertexAttribPointer(maAXy, 2, gl.FLOAT, false, 0, 0);
|
|
1400
|
-
gl.bufferSubData(
|
|
1401
|
-
gl.ARRAY_BUFFER,
|
|
1402
|
-
0,
|
|
1403
|
-
bufferEntry.vertices,
|
|
1404
|
-
0,
|
|
1405
|
-
drawVertexCount * 2
|
|
1406
|
-
);
|
|
1407
|
-
gl.drawArrays(gl.LINES, 0, drawVertexCount);
|
|
2027
|
+
descriptor,
|
|
2028
|
+
dpr,
|
|
2029
|
+
getAppendAnimatedPoint,
|
|
2030
|
+
movingAverage: movingAverageByChart?.[chart.id],
|
|
2031
|
+
now,
|
|
2032
|
+
pixelHeight,
|
|
2033
|
+
pixelWidth,
|
|
2034
|
+
plot,
|
|
2035
|
+
scaledPlot,
|
|
2036
|
+
seriesOrderByChart,
|
|
2037
|
+
state,
|
|
2038
|
+
yRange
|
|
1408
2039
|
});
|
|
1409
2040
|
nextAxisOverlays[chart.id] = createAxisOverlay({
|
|
1410
2041
|
chart,
|
|
2042
|
+
descriptor,
|
|
1411
2043
|
plot,
|
|
1412
2044
|
state,
|
|
1413
2045
|
yRange,
|
|
1414
|
-
seriesEndpoints,
|
|
2046
|
+
seriesEndpoints: result?.seriesEndpoints || /* @__PURE__ */ new Map(),
|
|
1415
2047
|
seriesOrderByChart
|
|
1416
2048
|
});
|
|
1417
2049
|
gl.disable(gl.SCISSOR_TEST);
|
|
1418
2050
|
});
|
|
1419
|
-
|
|
1420
|
-
gl.disable(gl.BLEND);
|
|
1421
|
-
}
|
|
2051
|
+
gl.disable(gl.BLEND);
|
|
1422
2052
|
return nextAxisOverlays;
|
|
1423
2053
|
};
|
|
1424
2054
|
|
|
1425
2055
|
// src/vanilla/createChartGrid.js
|
|
1426
2056
|
var BASE_ROOT_CLASSES = ["aliencharts-root", "relative", "h-full", "overflow-y-auto"];
|
|
1427
|
-
var
|
|
2057
|
+
var EMPTY_OBJECT = Object.freeze({});
|
|
2058
|
+
var controllerIdSequence = 0;
|
|
1428
2059
|
var DEFAULT_OPTIONS = Object.freeze({
|
|
1429
2060
|
charts: [],
|
|
1430
2061
|
columns: 2,
|
|
@@ -1439,7 +2070,7 @@ var DEFAULT_OPTIONS = Object.freeze({
|
|
|
1439
2070
|
followVisibleLatest: true,
|
|
1440
2071
|
xAxisLabel: "STEP",
|
|
1441
2072
|
disableDrawings: false,
|
|
1442
|
-
seriesOrderByChart:
|
|
2073
|
+
seriesOrderByChart: EMPTY_OBJECT,
|
|
1443
2074
|
topMarkers: [],
|
|
1444
2075
|
formatXTick: formatCompactNumber,
|
|
1445
2076
|
formatXValue: formatNumber,
|
|
@@ -1450,6 +2081,13 @@ var APPEND_ANIMATION = Object.freeze({
|
|
|
1450
2081
|
maxBucketSize: 64,
|
|
1451
2082
|
maxRevealPoints: 100
|
|
1452
2083
|
});
|
|
2084
|
+
var POINTER_SCALE_SENSITIVITY = 0.01;
|
|
2085
|
+
var WHEEL_SCALE_SENSITIVITY = 1e-3;
|
|
2086
|
+
var scaleValueRange = (scale, delta, sensitivity) => clamp(
|
|
2087
|
+
scale * Math.exp(delta * sensitivity),
|
|
2088
|
+
Y_SCALE_MIN,
|
|
2089
|
+
Y_SCALE_MAX
|
|
2090
|
+
);
|
|
1453
2091
|
var escapeHtml = (value) => String(value ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1454
2092
|
var withAlpha = (color, alpha) => {
|
|
1455
2093
|
const value = String(color || DEFAULT_CHART_BACKGROUND).trim();
|
|
@@ -1473,7 +2111,12 @@ var toolbarButton = ({ action, title, icon, hotkey = "", active = false, classNa
|
|
|
1473
2111
|
${icon}
|
|
1474
2112
|
${hotkey ? `<span class="pointer-events-none absolute left-full top-1/2 -translate-y-1/2 text-[10px] font-semibold leading-none text-foreground/50">${hotkey}</span>` : ""}
|
|
1475
2113
|
</button>`;
|
|
1476
|
-
var chartCellHtml = (chart, index, backgroundColor) =>
|
|
2114
|
+
var chartCellHtml = (chart, descriptor, index, backgroundColor) => {
|
|
2115
|
+
const padding = getPlotPadding(chart, descriptor);
|
|
2116
|
+
const horizontal = descriptor.orientation === "horizontal";
|
|
2117
|
+
const valueAxisStyle = horizontal ? `left:${padding.left}px;right:${padding.right}px;bottom:0;height:${padding.bottom}px;cursor:ew-resize` : `right:0;top:${padding.top}px;bottom:${padding.bottom}px;width:${padding.right}px;cursor:ns-resize`;
|
|
2118
|
+
const categoryAxisStyle = horizontal ? `left:0;top:${padding.top}px;bottom:${padding.bottom}px;width:${padding.left}px;cursor:ns-resize` : `left:${padding.left}px;right:${padding.right}px;bottom:0;height:${padding.bottom}px;cursor:ew-resize`;
|
|
2119
|
+
return `
|
|
1477
2120
|
<div data-chart-index="${index}" class="relative select-none overflow-hidden rounded-sm" style="height:${CHART_HEIGHT}px;background-color:${escapeHtml(backgroundColor)}">
|
|
1478
2121
|
<div class="pointer-events-none absolute inset-x-0 top-0 z-10 flex h-8 items-center px-2 backdrop-blur" data-header>
|
|
1479
2122
|
<div class="flex min-w-0 items-center gap-1 rounded-sm px-1 text-sm font-semibold text-foreground" data-title-wrap>
|
|
@@ -1483,25 +2126,19 @@ var chartCellHtml = (chart, index, backgroundColor) => `
|
|
|
1483
2126
|
</div>
|
|
1484
2127
|
<div data-grid-lines class="pointer-events-none absolute text-border/40"></div>
|
|
1485
2128
|
<div data-latest-lines></div>
|
|
1486
|
-
<div data-y-axis class="absolute
|
|
1487
|
-
<div data-x-axis class="absolute
|
|
2129
|
+
<div data-y-axis class="absolute z-20" style="${valueAxisStyle}"></div>
|
|
2130
|
+
<div data-x-axis class="absolute z-20" style="${categoryAxisStyle}"></div>
|
|
1488
2131
|
<div data-toolbar></div>
|
|
1489
2132
|
</div>`;
|
|
2133
|
+
};
|
|
1490
2134
|
var makeSurface = (canvas, gl) => ({
|
|
1491
2135
|
canvas,
|
|
1492
2136
|
gl,
|
|
1493
|
-
|
|
1494
|
-
antialiasProgram: createAntialiasProgram(gl),
|
|
1495
|
-
seriesBuffers: createSeriesBufferCache(),
|
|
1496
|
-
antialiasBuffers: createSeriesBufferCache(),
|
|
1497
|
-
movingAverageCache: /* @__PURE__ */ new Map()
|
|
2137
|
+
renderers: createRendererRegistry(gl)
|
|
1498
2138
|
});
|
|
1499
2139
|
var destroySurface = (surface) => {
|
|
1500
2140
|
if (!surface) return;
|
|
1501
|
-
|
|
1502
|
-
deleteSeriesBuffers(surface.gl, surface.antialiasBuffers);
|
|
1503
|
-
surface.gl.deleteProgram(surface.program);
|
|
1504
|
-
surface.gl.deleteProgram(surface.antialiasProgram);
|
|
2141
|
+
surface.renderers.destroy();
|
|
1505
2142
|
surface.gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
1506
2143
|
};
|
|
1507
2144
|
var AppendAnimator = class {
|
|
@@ -1511,29 +2148,33 @@ var AppendAnimator = class {
|
|
|
1511
2148
|
this.animations = /* @__PURE__ */ new Map();
|
|
1512
2149
|
this.frame = null;
|
|
1513
2150
|
}
|
|
1514
|
-
scan(charts) {
|
|
2151
|
+
scan(charts, descriptors) {
|
|
1515
2152
|
const now = performance.now();
|
|
1516
|
-
charts.forEach((chart) =>
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
2153
|
+
charts.forEach((chart) => {
|
|
2154
|
+
const descriptor = descriptors.get(chart.id);
|
|
2155
|
+
if (!descriptor?.capabilities.appendAnimation) return;
|
|
2156
|
+
chart.series.forEach((series) => {
|
|
2157
|
+
if (!series.length) return;
|
|
2158
|
+
const next = {
|
|
2159
|
+
x: series.rawX[series.length - 1],
|
|
2160
|
+
y: series.rawY[series.length - 1],
|
|
2161
|
+
length: series.length
|
|
2162
|
+
};
|
|
2163
|
+
const previous = this.latest.get(series.id);
|
|
2164
|
+
if (previous && next.x > previous.x) {
|
|
2165
|
+
this.animations.set(series.id, {
|
|
2166
|
+
fromX: previous.x,
|
|
2167
|
+
fromY: previous.y,
|
|
2168
|
+
toX: next.x,
|
|
2169
|
+
toY: next.y,
|
|
2170
|
+
appendedCount: next.length - previous.length,
|
|
2171
|
+
startedAt: now,
|
|
2172
|
+
duration: APPEND_ANIMATION.durationMs
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
this.latest.set(series.id, next);
|
|
2176
|
+
});
|
|
2177
|
+
});
|
|
1537
2178
|
if (this.animations.size) this.start();
|
|
1538
2179
|
}
|
|
1539
2180
|
start() {
|
|
@@ -1589,8 +2230,10 @@ var ChartGridController = class {
|
|
|
1589
2230
|
constructor(container, options) {
|
|
1590
2231
|
if (!(container instanceof HTMLElement)) throw new TypeError("createChartGrid requires an HTMLElement target");
|
|
1591
2232
|
this.container = container;
|
|
2233
|
+
this.controllerId = `aliencharts-${controllerIdSequence += 1}`;
|
|
1592
2234
|
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
1593
2235
|
if (!Array.isArray(this.options.charts)) throw new TypeError("charts must be an array");
|
|
2236
|
+
this.chartDescriptors = resolveChartDescriptors(this.options.charts);
|
|
1594
2237
|
this.destroyed = false;
|
|
1595
2238
|
this.frame = null;
|
|
1596
2239
|
this.structureDirty = true;
|
|
@@ -1631,7 +2274,7 @@ var ChartGridController = class {
|
|
|
1631
2274
|
this.surface = makeSurface(this.canvas, gl);
|
|
1632
2275
|
this.fullscreenSurface = null;
|
|
1633
2276
|
this.animator = new AppendAnimator(() => this.requestRender());
|
|
1634
|
-
this.animator.scan(this.options.charts);
|
|
2277
|
+
this.animator.scan(this.options.charts, this.chartDescriptors);
|
|
1635
2278
|
this.bound = {
|
|
1636
2279
|
click: (event) => this.handleClick(event),
|
|
1637
2280
|
input: (event) => this.handleInput(event),
|
|
@@ -1685,8 +2328,16 @@ var ChartGridController = class {
|
|
|
1685
2328
|
if (!next || typeof next !== "object") return;
|
|
1686
2329
|
const previousCharts = this.options.charts;
|
|
1687
2330
|
const previousActiveDrawingTool = this.activeDrawingTool;
|
|
1688
|
-
|
|
1689
|
-
if (!Array.isArray(
|
|
2331
|
+
const nextOptions = { ...this.options, ...next };
|
|
2332
|
+
if (!Array.isArray(nextOptions.charts)) throw new TypeError("charts must be an array");
|
|
2333
|
+
const nextDescriptors = resolveChartDescriptors(nextOptions.charts);
|
|
2334
|
+
const descriptorLayoutChanged = nextOptions.charts.some((chart) => {
|
|
2335
|
+
const previous = this.chartDescriptors.get(chart.id);
|
|
2336
|
+
const current = nextDescriptors.get(chart.id);
|
|
2337
|
+
return previous?.rendererType !== current?.rendererType || previous?.orientation !== current?.orientation || previous?.categorical !== current?.categorical;
|
|
2338
|
+
});
|
|
2339
|
+
this.options = nextOptions;
|
|
2340
|
+
this.chartDescriptors = nextDescriptors;
|
|
1690
2341
|
if (hasOwn(next, "drawings")) this.drawings = Array.isArray(next.drawings) ? [...next.drawings] : [];
|
|
1691
2342
|
if (hasOwn(next, "activeDrawingTool")) {
|
|
1692
2343
|
this.activeDrawingTool = next.activeDrawingTool ?? null;
|
|
@@ -1697,7 +2348,7 @@ var ChartGridController = class {
|
|
|
1697
2348
|
}
|
|
1698
2349
|
if (hasOwn(next, "selectedDrawingId")) this.selectedDrawingId = next.selectedDrawingId ?? null;
|
|
1699
2350
|
if (hasOwn(next, "movingAverageByChart")) this.movingAverageByChart = { ...next.movingAverageByChart || {} };
|
|
1700
|
-
if (previousCharts !== this.options.charts || hasOwn(next, "columns") || hasOwn(next, "backgroundColor") || hasOwn(next, "gridLines")) this.structureDirty = true;
|
|
2351
|
+
if (previousCharts !== this.options.charts || descriptorLayoutChanged || hasOwn(next, "columns") || hasOwn(next, "backgroundColor") || hasOwn(next, "gridLines")) this.structureDirty = true;
|
|
1701
2352
|
this.initializeChartState();
|
|
1702
2353
|
this.requestRender();
|
|
1703
2354
|
}
|
|
@@ -1707,7 +2358,7 @@ var ChartGridController = class {
|
|
|
1707
2358
|
invalidate() {
|
|
1708
2359
|
this.assertAlive();
|
|
1709
2360
|
this.followAppendedData();
|
|
1710
|
-
this.animator.scan(this.options.charts);
|
|
2361
|
+
this.animator.scan(this.options.charts, this.chartDescriptors);
|
|
1711
2362
|
this.requestRender();
|
|
1712
2363
|
}
|
|
1713
2364
|
followAppendedData() {
|
|
@@ -1733,7 +2384,10 @@ var ChartGridController = class {
|
|
|
1733
2384
|
const maxX = getChartXBounds(chart).maxX;
|
|
1734
2385
|
if (!Number.isFinite(maxX)) return;
|
|
1735
2386
|
const state = this.viewStates.get(chart.id) || getInitialView(chart, this.options.initialVisiblePoints);
|
|
1736
|
-
const span = Math.max(
|
|
2387
|
+
const span = Math.max(
|
|
2388
|
+
getMinimumCategorySpan(chart),
|
|
2389
|
+
state.xMax - state.xMin
|
|
2390
|
+
);
|
|
1737
2391
|
const nextMax = maxX + span * 0.1;
|
|
1738
2392
|
this.viewStates.set(chart.id, { xMin: nextMax - span, xMax: nextMax });
|
|
1739
2393
|
});
|
|
@@ -1745,7 +2399,12 @@ var ChartGridController = class {
|
|
|
1745
2399
|
}
|
|
1746
2400
|
renderStructure() {
|
|
1747
2401
|
this.grid.style.gridTemplateColumns = `repeat(${Math.max(1, Number(this.options.columns) || 1)}, minmax(18rem, 1fr))`;
|
|
1748
|
-
this.grid.innerHTML = this.options.charts.map((chart, index) => chartCellHtml(
|
|
2402
|
+
this.grid.innerHTML = this.options.charts.map((chart, index) => chartCellHtml(
|
|
2403
|
+
chart,
|
|
2404
|
+
this.chartDescriptors.get(chart.id),
|
|
2405
|
+
index,
|
|
2406
|
+
this.options.backgroundColor
|
|
2407
|
+
)).join("");
|
|
1749
2408
|
this.chartNodes = [...this.grid.querySelectorAll("[data-chart-index]")];
|
|
1750
2409
|
this.chartNodes.forEach((node) => {
|
|
1751
2410
|
node.querySelector("[data-header]").style.backgroundColor = withAlpha(this.options.backgroundColor, 0.82);
|
|
@@ -1762,14 +2421,17 @@ var ChartGridController = class {
|
|
|
1762
2421
|
const x = rect.left - hostRect.left + host.scrollLeft;
|
|
1763
2422
|
const y = rect.top - hostRect.top + host.scrollTop;
|
|
1764
2423
|
const chart = charts[index];
|
|
2424
|
+
const descriptor = this.chartDescriptors.get(chart.id);
|
|
2425
|
+
const padding = getPlotPadding(chart, descriptor);
|
|
1765
2426
|
const layout = {
|
|
1766
2427
|
chart,
|
|
2428
|
+
descriptor,
|
|
1767
2429
|
rect: { x, y, width: rect.width, height: rect.height },
|
|
1768
2430
|
plot: {
|
|
1769
|
-
x: x +
|
|
1770
|
-
y: y +
|
|
1771
|
-
width: Math.max(1, rect.width -
|
|
1772
|
-
height: Math.max(1, rect.height -
|
|
2431
|
+
x: x + padding.left,
|
|
2432
|
+
y: y + padding.top,
|
|
2433
|
+
width: Math.max(1, rect.width - padding.left - padding.right),
|
|
2434
|
+
height: Math.max(1, rect.height - padding.top - padding.bottom)
|
|
1773
2435
|
},
|
|
1774
2436
|
visible: fullscreen || rect.bottom >= hostRect.top && rect.top <= hostRect.bottom,
|
|
1775
2437
|
node,
|
|
@@ -1823,72 +2485,85 @@ var ChartGridController = class {
|
|
|
1823
2485
|
width: Math.max(1, width),
|
|
1824
2486
|
height: Math.max(1, height),
|
|
1825
2487
|
gl: surface.gl,
|
|
1826
|
-
|
|
1827
|
-
antialiasProgram: surface.antialiasProgram,
|
|
2488
|
+
rendererRegistry: surface.renderers,
|
|
1828
2489
|
antialiasLines: this.options.antialiasLines,
|
|
1829
2490
|
layouts,
|
|
1830
2491
|
viewStateRef: { current: this.viewStates },
|
|
1831
2492
|
yScaleRef: { current: this.yScales },
|
|
1832
2493
|
yCenterOffsetRef: { current: this.yOffsets },
|
|
1833
|
-
seriesBufferCache: surface.seriesBuffers,
|
|
1834
|
-
antialiasSeriesBufferCache: surface.antialiasBuffers,
|
|
1835
2494
|
initialVisiblePoints: this.options.initialVisiblePoints,
|
|
1836
2495
|
getAppendAnimatedPoint: (payload) => this.animator.getPoint(payload),
|
|
1837
2496
|
movingAverageByChart: this.movingAverageByChart,
|
|
1838
|
-
movingAverageCache: surface.movingAverageCache,
|
|
1839
2497
|
seriesOrderByChart: this.options.seriesOrderByChart
|
|
1840
2498
|
});
|
|
1841
2499
|
}
|
|
1842
2500
|
updateCells(layouts, axes, fullscreen) {
|
|
1843
|
-
layouts.forEach(({ chart, node }) => {
|
|
2501
|
+
layouts.forEach(({ chart, descriptor, node }) => {
|
|
1844
2502
|
const axis = axes[chart.id];
|
|
2503
|
+
const padding = getPlotPadding(chart, descriptor);
|
|
2504
|
+
const horizontal = axis?.orientation === "horizontal";
|
|
1845
2505
|
node.querySelector("[data-title-wrap]").classList.toggle("ring-1", this.focusedChartId === chart.id);
|
|
1846
2506
|
node.querySelector("[data-title-wrap]").classList.toggle("ring-primary/70", this.focusedChartId === chart.id);
|
|
1847
2507
|
const grid = node.querySelector("[data-grid-lines]");
|
|
1848
2508
|
const gridOptions = this.options.gridLines === true ? {} : this.options.gridLines || {};
|
|
1849
2509
|
grid.style.display = this.options.gridLines ? "block" : "none";
|
|
1850
2510
|
Object.assign(grid.style, {
|
|
1851
|
-
left: `${
|
|
1852
|
-
right: `${
|
|
1853
|
-
top: `${
|
|
1854
|
-
bottom: `${
|
|
2511
|
+
left: `${padding.left}px`,
|
|
2512
|
+
right: `${padding.right}px`,
|
|
2513
|
+
top: `${padding.top}px`,
|
|
2514
|
+
bottom: `${padding.bottom}px`,
|
|
1855
2515
|
backgroundImage: "linear-gradient(to right, transparent calc(100% - 1px), currentColor calc(100% - 1px)), linear-gradient(to bottom, transparent calc(100% - 1px), currentColor calc(100% - 1px))",
|
|
1856
2516
|
backgroundSize: `${Math.max(8, Number(gridOptions.xSpacing) || 80)}px 100%, 100% ${Math.max(8, Number(gridOptions.ySpacing) || 48)}px`
|
|
1857
2517
|
});
|
|
1858
2518
|
const yAxis = node.querySelector("[data-y-axis]");
|
|
1859
2519
|
yAxis.style.backgroundColor = withAlpha(this.options.backgroundColor, 0.78);
|
|
1860
|
-
yAxis.innerHTML = (axis?.ticks || []).map((tick) => `<div class="pointer-events-none absolute left-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-[11px] font-medium tabular-nums text-foreground/70" style="top:${tick.top}px">${escapeHtml(this.options.formatYValue(tick.value))}</div>`).join("") + (axis?.latestValues || []).map((latest) => `<div class="pointer-events-none absolute left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-sm px-1.5 py-0.5 text-center text-[10px] font-bold tabular-nums shadow-sm" style="top:${latest.top}px;background:${latest.color};color:${getTextColor(latest.color)}">${escapeHtml(this.options.formatYValue(latest.value))}</div>`).join("");
|
|
2520
|
+
yAxis.innerHTML = (axis?.ticks || []).map((tick) => horizontal ? `<div class="pointer-events-none absolute top-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-[11px] font-medium tabular-nums text-foreground/70" style="left:${tick.left}px">${escapeHtml(this.options.formatYValue(tick.value))}</div>` : `<div class="pointer-events-none absolute left-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-[11px] font-medium tabular-nums text-foreground/70" style="top:${tick.top}px">${escapeHtml(this.options.formatYValue(tick.value))}</div>`).join("") + (axis?.latestValues || []).map((latest) => `<div class="pointer-events-none absolute left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-sm px-1.5 py-0.5 text-center text-[10px] font-bold tabular-nums shadow-sm" style="top:${latest.top}px;background:${latest.color};color:${getTextColor(latest.color)}">${escapeHtml(this.options.formatYValue(latest.value))}</div>`).join("");
|
|
1861
2521
|
const xAxis = node.querySelector("[data-x-axis]");
|
|
1862
2522
|
xAxis.style.backgroundColor = withAlpha(this.options.backgroundColor, 0.7);
|
|
1863
|
-
xAxis.innerHTML = (axis?.xTicks || []).map((tick) =>
|
|
2523
|
+
xAxis.innerHTML = (axis?.xTicks || []).map((tick) => {
|
|
2524
|
+
const label = tick.categorical ? tick.label : this.options.formatXTick(tick.value);
|
|
2525
|
+
if (horizontal && tick.categorical) {
|
|
2526
|
+
return `<div data-category-label title="${escapeHtml(label)}" class="pointer-events-none absolute inset-x-2 -translate-y-1/2 truncate text-right text-[11px] font-medium text-foreground/70" style="top:${tick.top}px">${escapeHtml(label)}</div>`;
|
|
2527
|
+
}
|
|
2528
|
+
if (tick.categorical) {
|
|
2529
|
+
return `<div data-category-label title="${escapeHtml(label)}" class="pointer-events-none absolute top-1/2 w-24 -translate-x-1/2 -translate-y-1/2 truncate text-center text-[11px] font-medium text-foreground/70" style="left:${tick.left}px">${escapeHtml(label)}</div>`;
|
|
2530
|
+
}
|
|
2531
|
+
return horizontal ? `<div class="pointer-events-none absolute left-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-[11px] font-medium tabular-nums text-foreground/70" style="top:${tick.top}px">${escapeHtml(label)}</div>` : `<div class="pointer-events-none absolute top-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-[11px] font-medium tabular-nums text-foreground/70" style="left:${tick.left}px">${escapeHtml(label)}</div>`;
|
|
2532
|
+
}).join("");
|
|
1864
2533
|
const latestLayer = node.querySelector("[data-latest-lines]");
|
|
1865
|
-
latestLayer.innerHTML = this.options.showLatestValueLine ? (axis?.latestValues || []).filter((item) => item.left >= 0 && item.left <= axis.plotWidth).map((item) => `<div class="pointer-events-none absolute z-10 opacity-70" style="left:${
|
|
2534
|
+
latestLayer.innerHTML = this.options.showLatestValueLine ? (axis?.latestValues || []).filter((item) => item.left >= 0 && item.left <= axis.plotWidth).map((item) => `<div class="pointer-events-none absolute z-10 opacity-70" style="left:${padding.left + item.left}px;top:${padding.top + item.top}px;height:1px;width:${Math.max(0, axis.plotWidth - item.left)}px;background-image:repeating-linear-gradient(to right,${item.color} 0 4px,transparent 4px 8px)"></div>`).join("") : "";
|
|
1866
2535
|
this.updateToolbar(node, chart, axis, fullscreen);
|
|
1867
2536
|
});
|
|
1868
2537
|
}
|
|
1869
2538
|
updateToolbar(node, chart, axis, fullscreen) {
|
|
1870
2539
|
const target = node.querySelector("[data-toolbar]");
|
|
1871
2540
|
const focused = fullscreen || this.focusedChartId === chart.id;
|
|
2541
|
+
const descriptor = this.chartDescriptors.get(chart.id);
|
|
2542
|
+
const drawingsAvailable = descriptor.capabilities.drawings;
|
|
2543
|
+
const movingAverageAvailable = descriptor.capabilities.movingAverage;
|
|
2544
|
+
const padding = getPlotPadding(chart, descriptor);
|
|
1872
2545
|
let html = "";
|
|
1873
|
-
if (axis?.showJumpLatest) html += `<button type="button" data-action="jump-latest" aria-label="Jump to latest" class="absolute z-30 inline-flex size-7 items-center justify-center rounded-sm text-foreground hover:bg-accent" style="right:${
|
|
2546
|
+
if (axis?.showJumpLatest) html += `<button type="button" data-action="jump-latest" aria-label="Jump to latest" class="absolute z-30 inline-flex size-7 items-center justify-center rounded-sm text-foreground hover:bg-accent" style="right:${padding.right + 6}px;bottom:${padding.bottom + 6}px;background:${withAlpha(this.options.backgroundColor, 0.7)}">${iconSvg("arrowLineRight", { className: axis.orientation === "horizontal" ? "rotate-90" : "" })}</button>`;
|
|
1874
2547
|
if (focused && this.options.showToolbar) {
|
|
1875
|
-
const moving = Boolean(this.movingAverageByChart[chart.id]?.enabled);
|
|
2548
|
+
const moving = movingAverageAvailable && Boolean(this.movingAverageByChart[chart.id]?.enabled);
|
|
1876
2549
|
const hasDrawings = getDrawingsForChart(this.drawings, chart.id).length > 0;
|
|
1877
2550
|
html += `<div class="absolute left-2 top-12 z-30 flex flex-col gap-0.5 rounded-sm bg-background/80 pr-3 text-foreground shadow-sm backdrop-blur">`;
|
|
1878
2551
|
html += toolbarButton({ action: "fullscreen", title: fullscreen ? "Exit fullscreen" : "Maximize chart", hotkey: "F", icon: iconSvg(fullscreen ? "arrowsIn" : "arrowsOut") });
|
|
1879
2552
|
html += toolbarButton({ action: "reset", title: "Reset chart", hotkey: "R", icon: iconSvg("arrowCounterClockwise") });
|
|
1880
2553
|
html += toolbarButton({ action: "rectangle-zoom", title: "Rectangle zoom", hotkey: "Z", active: this.rectangleZoomChartId === chart.id, icon: iconSvg("magnifyingGlassPlus") });
|
|
1881
|
-
if (!this.options.disableDrawings && hasDrawings) html += toolbarButton({ action: "clear-drawings", title: "Clear drawings", className: "text-orange-500", icon: iconSvg("broom") });
|
|
1882
|
-
if (!this.options.disableDrawings) {
|
|
2554
|
+
if (drawingsAvailable && !this.options.disableDrawings && hasDrawings) html += toolbarButton({ action: "clear-drawings", title: "Clear drawings", className: "text-orange-500", icon: iconSvg("broom") });
|
|
2555
|
+
if ((drawingsAvailable || movingAverageAvailable) && !this.options.disableDrawings) {
|
|
1883
2556
|
html += '<div class="my-1 h-px w-full"></div>';
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2557
|
+
if (drawingsAvailable) {
|
|
2558
|
+
html += toolbarButton({ action: "tool-trendline", title: "Draw trendline", hotkey: "T", active: this.activeDrawingTool === "trendline", icon: iconSvg("minus", { className: "-rotate-45" }) });
|
|
2559
|
+
html += toolbarButton({ action: "tool-hline", title: "Draw horizontal line", hotkey: "H", active: this.activeDrawingTool === "hline", icon: iconSvg("minus") });
|
|
2560
|
+
html += toolbarButton({ action: "tool-vline", title: "Draw vertical line", hotkey: "V", active: this.activeDrawingTool === "vline", icon: iconSvg("minus", { className: "rotate-90" }) });
|
|
2561
|
+
html += toolbarButton({ action: "tool-pin", title: "Place pin", hotkey: "P", active: this.activeDrawingTool === "pin", icon: iconSvg("mapPin") });
|
|
2562
|
+
}
|
|
2563
|
+
if (movingAverageAvailable) html += toolbarButton({ action: "moving-average", title: "Toggle moving average", hotkey: "M", active: moving, icon: iconSvg("waveSine") });
|
|
1889
2564
|
}
|
|
1890
2565
|
html += "</div>";
|
|
1891
|
-
if (!this.options.disableDrawings && moving) html += this.movingAverageOptionsHtml(chart);
|
|
2566
|
+
if (movingAverageAvailable && !this.options.disableDrawings && moving) html += this.movingAverageOptionsHtml(chart);
|
|
1892
2567
|
}
|
|
1893
2568
|
target.innerHTML = html;
|
|
1894
2569
|
}
|
|
@@ -1916,21 +2591,35 @@ var ChartGridController = class {
|
|
|
1916
2591
|
drawingOverlayHtml(layouts) {
|
|
1917
2592
|
const drawings = this.draftDrawing ? [...this.drawings, this.draftDrawing] : this.drawings;
|
|
1918
2593
|
if (!drawings.length) return "";
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
}
|
|
1927
|
-
return `<
|
|
1928
|
-
})
|
|
1929
|
-
|
|
2594
|
+
const drawableLayouts = layouts.map((layout, index) => ({
|
|
2595
|
+
layout,
|
|
2596
|
+
index,
|
|
2597
|
+
drawings: layout.descriptor.capabilities.drawings ? getDrawingsForChart(drawings, layout.chart.id) : []
|
|
2598
|
+
})).filter((entry) => entry.drawings.length > 0);
|
|
2599
|
+
if (!drawableLayouts.length) return "";
|
|
2600
|
+
const definitions = drawableLayouts.map(({ layout, index }) => {
|
|
2601
|
+
const clipId = `${this.controllerId}-drawing-clip-${layout.fullscreen ? "fullscreen" : "grid"}-${index}`;
|
|
2602
|
+
return `<clipPath id="${clipId}"><rect data-drawing-clip="${escapeHtml(layout.chart.id)}" x="${layout.plot.x}" y="${layout.plot.y}" width="${layout.plot.width}" height="${layout.plot.height}"/></clipPath>`;
|
|
2603
|
+
}).join("");
|
|
2604
|
+
const body = drawableLayouts.map(({ layout, index, drawings: chartDrawings }) => {
|
|
2605
|
+
const clipId = `${this.controllerId}-drawing-clip-${layout.fullscreen ? "fullscreen" : "grid"}-${index}`;
|
|
2606
|
+
const chartBody = chartDrawings.map((drawing) => {
|
|
2607
|
+
const geometry = getDrawingGeometry({ drawing, layout, projectPoint: (point) => this.projectPoint(point, layout) });
|
|
2608
|
+
if (!geometry) return "";
|
|
2609
|
+
const selected = drawing.id === this.selectedDrawingId || drawing.id === "__draft__";
|
|
2610
|
+
if (drawing.type === "pin") {
|
|
2611
|
+
const text = escapeHtml(geometry.style.text?.trim() || "");
|
|
2612
|
+
return `<g opacity="${drawing.id === "__draft__" ? 0.72 : 1}"><g transform="translate(${geometry.start.x} ${geometry.start.y})"><path d="M0 -11 C-6 -11 -10 -6 -10 -1 C-10 6 -2 12 0 15 C2 12 10 6 10 -1 C10 -6 6 -11 0 -11 Z" fill="${geometry.style.color}" stroke="var(--background,#fff)" stroke-width="1.5"/><circle cy="-2" r="3" fill="var(--background,#fff)"/></g>${text ? `<text x="${geometry.start.x + 14}" y="${geometry.start.y}" fill="${geometry.style.color}" stroke="var(--background,#fff)" stroke-width="3" paint-order="stroke" font-size="12" font-weight="600">${text}</text>` : ""}${selected ? `<circle cx="${geometry.start.x}" cy="${geometry.start.y}" r="4" fill="${geometry.style.color}" stroke="var(--background,#fff)"/>` : ""}</g>`;
|
|
2613
|
+
}
|
|
2614
|
+
return `<g><line data-drawing-line x1="${geometry.lineStart.x}" y1="${geometry.lineStart.y}" x2="${geometry.lineEnd.x}" y2="${geometry.lineEnd.y}" stroke="${geometry.style.color}" stroke-width="${geometry.style.lineWidth}" stroke-dasharray="${geometry.style.dashPattern.join(" ")}" opacity="${drawing.id === "__draft__" ? 0.72 : 1}"/>${selected ? `<circle data-drawing-anchor="start" cx="${geometry.start.x}" cy="${geometry.start.y}" r="4" fill="${geometry.style.color}" stroke="var(--background,#fff)"/>${drawing.type === "trendline" ? `<circle data-drawing-anchor="end" cx="${geometry.end.x}" cy="${geometry.end.y}" r="4" fill="${geometry.style.color}" stroke="var(--background,#fff)"/>` : ""}` : ""}</g>`;
|
|
2615
|
+
}).join("");
|
|
2616
|
+
return `<g data-drawing-chart="${escapeHtml(layout.chart.id)}" clip-path="url(#${clipId})">${chartBody}</g>`;
|
|
2617
|
+
}).join("");
|
|
2618
|
+
return `<svg class="pointer-events-none absolute left-0 top-0 z-20 block overflow-visible" style="width:100%;height:100%"><defs>${definitions}</defs>${body}</svg>`;
|
|
1930
2619
|
}
|
|
1931
2620
|
topMarkersHtml(layouts) {
|
|
1932
2621
|
if (!this.options.topMarkers?.length) return "";
|
|
1933
|
-
return layouts.flatMap((layout) => this.options.topMarkers.map((marker, markerIndex) => {
|
|
2622
|
+
return layouts.flatMap((layout) => !layout.descriptor.capabilities.markers ? [] : this.options.topMarkers.map((marker, markerIndex) => {
|
|
1934
2623
|
const value = Number(marker.x ?? marker.step);
|
|
1935
2624
|
const state = this.viewStates.get(layout.chart.id);
|
|
1936
2625
|
const ratio = (value - state.xMin) / (state.xMax - state.xMin);
|
|
@@ -1941,9 +2630,13 @@ var ChartGridController = class {
|
|
|
1941
2630
|
crosshairHtml() {
|
|
1942
2631
|
const crosshair = this.options.showTooltips ? this.crosshair : null;
|
|
1943
2632
|
if (!crosshair) return "";
|
|
2633
|
+
const chart = this.options.charts.find(
|
|
2634
|
+
(item) => item.id === crosshair.chartId
|
|
2635
|
+
);
|
|
2636
|
+
const xAxisLabel = chart?.xAxisLabel ?? this.options.xAxisLabel;
|
|
1944
2637
|
return `<div class="pointer-events-none absolute z-20 border-l border-foreground/40" style="left:${crosshair.x}px;top:0;height:100%"></div><div class="pointer-events-none absolute z-20 border-t border-foreground/40" style="top:${crosshair.y}px;left:0;width:100%"></div>
|
|
1945
2638
|
${crosshair.points.map((point) => `<div class="pointer-events-none absolute z-30 size-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full border border-background" style="left:${point.x}px;top:${point.y}px;background:${point.color}"></div>`).join("")}
|
|
1946
|
-
<div data-crosshair-tooltip class="pointer-events-none absolute z-30 w-[220px] rounded-sm border border-border/70 bg-popover/80 px-2 py-1.5 text-xs text-popover-foreground shadow-sm backdrop-blur-sm" style="left:${crosshair.tooltipX}px;top:${crosshair.tooltipY}px"><div class="mb-1 font-medium">${escapeHtml(
|
|
2639
|
+
<div data-crosshair-tooltip class="pointer-events-none absolute z-30 w-[220px] rounded-sm border border-border/70 bg-popover/80 px-2 py-1.5 text-xs text-popover-foreground shadow-sm backdrop-blur-sm" style="left:${crosshair.tooltipX}px;top:${crosshair.tooltipY}px"><div class="mb-1 font-medium">${escapeHtml(xAxisLabel)}: ${escapeHtml(crosshair.categoryLabel ?? this.options.formatXValue(crosshair.xValue))}</div>${crosshair.points.map((point) => `<div class="grid grid-cols-[auto_1fr] gap-x-2"><span class="mt-1 size-2 rounded-full" style="background:${point.color}"></span><div class="min-w-0"><div class="truncate text-muted-foreground">${escapeHtml(point.name)}</div><div class="tabular-nums">${escapeHtml(this.options.formatYValue(point.yValue))}</div></div></div>`).join("")}</div>`;
|
|
1947
2640
|
}
|
|
1948
2641
|
rectangleOverlayHtml() {
|
|
1949
2642
|
const rect = this.rectangleZoomRect && normalizeRect(this.rectangleZoomRect.start, this.rectangleZoomRect.end);
|
|
@@ -1953,7 +2646,7 @@ var ChartGridController = class {
|
|
|
1953
2646
|
const drawing = this.drawings.find((item) => item.id === this.selectedDrawingId);
|
|
1954
2647
|
if (!drawing) return "";
|
|
1955
2648
|
const layout = layouts.find((item) => item.chart.id === drawing.chartId);
|
|
1956
|
-
if (!layout) return "";
|
|
2649
|
+
if (!layout || !layout.descriptor.capabilities.drawings) return "";
|
|
1957
2650
|
const style = drawing.style || {};
|
|
1958
2651
|
const canExtend = drawing.type === "trendline" || drawing.type === "hline";
|
|
1959
2652
|
const canText = drawing.type === "pin";
|
|
@@ -1969,8 +2662,33 @@ var ChartGridController = class {
|
|
|
1969
2662
|
}
|
|
1970
2663
|
projectPoint(point, layout) {
|
|
1971
2664
|
const state = this.viewStates.get(layout.chart.id);
|
|
1972
|
-
const range =
|
|
1973
|
-
return
|
|
2665
|
+
const range = this.getRange(layout, state);
|
|
2666
|
+
return this.getTransform(layout, state, range).dataToScreen(point);
|
|
2667
|
+
}
|
|
2668
|
+
getRange(layout, state = this.viewStates.get(layout.chart.id)) {
|
|
2669
|
+
return applyYScale(
|
|
2670
|
+
getYRange(
|
|
2671
|
+
layout.chart,
|
|
2672
|
+
state.xMin,
|
|
2673
|
+
state.xMax,
|
|
2674
|
+
getCategoryPixelLength(
|
|
2675
|
+
layout.chart,
|
|
2676
|
+
layout.plot,
|
|
2677
|
+
layout.descriptor
|
|
2678
|
+
),
|
|
2679
|
+
layout.descriptor
|
|
2680
|
+
),
|
|
2681
|
+
this.yScales.get(layout.chart.id),
|
|
2682
|
+
this.yOffsets.get(layout.chart.id)
|
|
2683
|
+
);
|
|
2684
|
+
}
|
|
2685
|
+
getTransform(layout, state, range) {
|
|
2686
|
+
return createCoordinateTransform({
|
|
2687
|
+
orientation: layout.descriptor.orientation,
|
|
2688
|
+
categoryRange: { min: state.xMin, max: state.xMax },
|
|
2689
|
+
valueRange: { min: range.minY, max: range.maxY },
|
|
2690
|
+
plot: layout.plot
|
|
2691
|
+
});
|
|
1974
2692
|
}
|
|
1975
2693
|
pointForEvent(event, host) {
|
|
1976
2694
|
const rect = host.getBoundingClientRect();
|
|
@@ -2065,8 +2783,8 @@ var ChartGridController = class {
|
|
|
2065
2783
|
this.activePointerContext = context;
|
|
2066
2784
|
this.focusedChartId = layout.chart.id;
|
|
2067
2785
|
const inPlot = point.x >= layout.plot.x && point.x <= layout.plot.x + layout.plot.width && point.y >= layout.plot.y && point.y <= layout.plot.y + layout.plot.height;
|
|
2068
|
-
if (inPlot && this.activeDrawingTool && !this.options.disableDrawings) {
|
|
2069
|
-
const dataPoint = screenPointToDataPoint({ point, chart: layout.chart, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
|
|
2786
|
+
if (inPlot && this.activeDrawingTool && !this.options.disableDrawings && layout.descriptor.capabilities.drawings) {
|
|
2787
|
+
const dataPoint = screenPointToDataPoint({ point, chart: layout.chart, descriptor: layout.descriptor, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
|
|
2070
2788
|
if (["hline", "vline", "pin"].includes(this.activeDrawingTool)) {
|
|
2071
2789
|
this.commitDrawing(layout.chart.id, this.activeDrawingTool, dataPoint, dataPoint);
|
|
2072
2790
|
} else if (this.drawingSession?.chartId === layout.chart.id) {
|
|
@@ -2079,7 +2797,7 @@ var ChartGridController = class {
|
|
|
2079
2797
|
this.drag = { type: "rectangle", layout, start: point };
|
|
2080
2798
|
this.rectangleZoomRect = { start: point, end: point };
|
|
2081
2799
|
} else if (inPlot) {
|
|
2082
|
-
const hit = this.hitDrawing(point, layout);
|
|
2800
|
+
const hit = layout.descriptor.capabilities.drawings ? this.hitDrawing(point, layout) : null;
|
|
2083
2801
|
if (hit) {
|
|
2084
2802
|
this.selectedDrawingId = hit.drawing.id;
|
|
2085
2803
|
this.emit("onSelectedDrawingIdChange", hit.drawing.id);
|
|
@@ -2091,9 +2809,13 @@ var ChartGridController = class {
|
|
|
2091
2809
|
this.emit("onSelectedDrawingIdChange", null);
|
|
2092
2810
|
this.drag = { type: "pan", layout, start: point, state: { ...this.viewStates.get(layout.chart.id) }, yOffset: this.yOffsets.get(layout.chart.id) || 0 };
|
|
2093
2811
|
}
|
|
2094
|
-
} else if (point.x >= layout.plot.x + layout.plot.width) {
|
|
2812
|
+
} else if (layout.descriptor.orientation === "vertical" && point.x >= layout.plot.x + layout.plot.width) {
|
|
2813
|
+
this.drag = { type: "y-scale", layout, start: point, scale: this.yScales.get(layout.chart.id) || 1 };
|
|
2814
|
+
} else if (layout.descriptor.orientation === "horizontal" && point.y >= layout.plot.y + layout.plot.height) {
|
|
2095
2815
|
this.drag = { type: "y-scale", layout, start: point, scale: this.yScales.get(layout.chart.id) || 1 };
|
|
2096
|
-
} else if (point.y >= layout.plot.y + layout.plot.height) {
|
|
2816
|
+
} else if (layout.descriptor.orientation === "vertical" && point.y >= layout.plot.y + layout.plot.height) {
|
|
2817
|
+
this.drag = { type: "x-scale", layout, start: point, state: { ...this.viewStates.get(layout.chart.id) } };
|
|
2818
|
+
} else if (layout.descriptor.orientation === "horizontal" && point.x <= layout.plot.x) {
|
|
2097
2819
|
this.drag = { type: "x-scale", layout, start: point, state: { ...this.viewStates.get(layout.chart.id) } };
|
|
2098
2820
|
}
|
|
2099
2821
|
context.host.setPointerCapture?.(event.pointerId);
|
|
@@ -2107,15 +2829,15 @@ var ChartGridController = class {
|
|
|
2107
2829
|
const { layout, point } = context;
|
|
2108
2830
|
if (this.drawingSession) {
|
|
2109
2831
|
if (layout.chart.id !== this.drawingSession.chartId) return;
|
|
2110
|
-
const end = screenPointToDataPoint({ point, chart: layout.chart, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
|
|
2832
|
+
const end = screenPointToDataPoint({ point, chart: layout.chart, descriptor: layout.descriptor, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
|
|
2111
2833
|
this.draftDrawing = createDraftDrawing({ ...this.drawingSession, end });
|
|
2112
2834
|
this.requestRender();
|
|
2113
2835
|
return;
|
|
2114
2836
|
}
|
|
2115
2837
|
if (this.drag) {
|
|
2116
|
-
const { chart, plot } = this.drag.layout;
|
|
2838
|
+
const { chart, descriptor, plot } = this.drag.layout;
|
|
2117
2839
|
if (this.drag.type === "drawing-edit") {
|
|
2118
|
-
const dataPoint = screenPointToDataPoint({ point, chart, plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
|
|
2840
|
+
const dataPoint = screenPointToDataPoint({ point, chart, descriptor, plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
|
|
2119
2841
|
const next = updateDrawingById(this.drawings, this.drag.drawingId, (drawing) => {
|
|
2120
2842
|
if (drawing.type === "hline") {
|
|
2121
2843
|
const deltaX = (drawing.end?.x ?? drawing.start.x) - drawing.start.x;
|
|
@@ -2134,21 +2856,73 @@ var ChartGridController = class {
|
|
|
2134
2856
|
if (this.drag.type === "rectangle") this.rectangleZoomRect = { start: this.drag.start, end: point };
|
|
2135
2857
|
if (this.drag.type === "pan") {
|
|
2136
2858
|
const span = this.drag.state.xMax - this.drag.state.xMin;
|
|
2137
|
-
const
|
|
2138
|
-
|
|
2859
|
+
const baseRange = getYRange(
|
|
2860
|
+
chart,
|
|
2861
|
+
this.drag.state.xMin,
|
|
2862
|
+
this.drag.state.xMax,
|
|
2863
|
+
getCategoryPixelLength(chart, plot, descriptor),
|
|
2864
|
+
descriptor
|
|
2865
|
+
);
|
|
2139
2866
|
const rangeSpan = (baseRange.maxY - baseRange.minY) * (this.yScales.get(chart.id) || 1);
|
|
2140
|
-
const
|
|
2141
|
-
|
|
2142
|
-
|
|
2867
|
+
const transform = createCoordinateTransform({
|
|
2868
|
+
orientation: descriptor.orientation,
|
|
2869
|
+
categoryRange: {
|
|
2870
|
+
min: this.drag.state.xMin,
|
|
2871
|
+
max: this.drag.state.xMax
|
|
2872
|
+
},
|
|
2873
|
+
valueRange: { min: 0, max: rangeSpan },
|
|
2874
|
+
plot
|
|
2875
|
+
});
|
|
2876
|
+
const categoryDelta = transform.categoryDragDelta(
|
|
2877
|
+
this.drag.start,
|
|
2878
|
+
point
|
|
2879
|
+
);
|
|
2880
|
+
const valueDelta = transform.valueOffsetDelta(
|
|
2881
|
+
this.drag.start,
|
|
2882
|
+
point
|
|
2883
|
+
);
|
|
2884
|
+
this.viewStates.set(chart.id, { xMin: this.drag.state.xMin - categoryDelta, xMax: this.drag.state.xMax - categoryDelta });
|
|
2885
|
+
this.yOffsets.set(chart.id, this.drag.yOffset + valueDelta);
|
|
2886
|
+
}
|
|
2887
|
+
if (this.drag.type === "y-scale") {
|
|
2888
|
+
const transform = createCoordinateTransform({
|
|
2889
|
+
orientation: descriptor.orientation,
|
|
2890
|
+
categoryRange: { min: 0, max: 1 },
|
|
2891
|
+
valueRange: { min: 0, max: 1 },
|
|
2892
|
+
plot
|
|
2893
|
+
});
|
|
2894
|
+
const delta = transform.valueScaleDelta(this.drag.start, point);
|
|
2895
|
+
this.yScales.set(
|
|
2896
|
+
chart.id,
|
|
2897
|
+
scaleValueRange(
|
|
2898
|
+
this.drag.scale,
|
|
2899
|
+
delta,
|
|
2900
|
+
POINTER_SCALE_SENSITIVITY
|
|
2901
|
+
)
|
|
2902
|
+
);
|
|
2143
2903
|
}
|
|
2144
|
-
if (this.drag.type === "y-scale") this.yScales.set(chart.id, clamp(this.drag.scale * Math.exp((point.y - this.drag.start.y) * 0.01), Y_SCALE_MIN, Y_SCALE_MAX));
|
|
2145
2904
|
if (this.drag.type === "x-scale") {
|
|
2146
2905
|
const center = (this.drag.state.xMin + this.drag.state.xMax) / 2;
|
|
2147
|
-
const
|
|
2906
|
+
const transform = createCoordinateTransform({
|
|
2907
|
+
orientation: descriptor.orientation,
|
|
2908
|
+
categoryRange: { min: 0, max: 1 },
|
|
2909
|
+
valueRange: { min: 0, max: 1 },
|
|
2910
|
+
plot
|
|
2911
|
+
});
|
|
2912
|
+
const delta = transform.categoryScaleDelta(this.drag.start, point);
|
|
2913
|
+
const span = clamp(
|
|
2914
|
+
(this.drag.state.xMax - this.drag.state.xMin) * Math.exp(delta * 0.01),
|
|
2915
|
+
getMinimumCategorySpan(chart),
|
|
2916
|
+
X_SCALE_MAX_SPAN
|
|
2917
|
+
);
|
|
2148
2918
|
this.viewStates.set(chart.id, { xMin: center - span / 2, xMax: center + span / 2 });
|
|
2149
2919
|
}
|
|
2150
|
-
if (this.drag.type === "pan")
|
|
2151
|
-
|
|
2920
|
+
if (this.drag.type === "pan") {
|
|
2921
|
+
this.updateCrosshair(point, layout);
|
|
2922
|
+
this.requestRender();
|
|
2923
|
+
} else {
|
|
2924
|
+
this.requestRender();
|
|
2925
|
+
}
|
|
2152
2926
|
return;
|
|
2153
2927
|
}
|
|
2154
2928
|
this.updateCrosshair(point, layout, context.fullscreen);
|
|
@@ -2162,6 +2936,7 @@ var ChartGridController = class {
|
|
|
2162
2936
|
const layout = this.drag.layout;
|
|
2163
2937
|
applyRectangleZoom({
|
|
2164
2938
|
chart: layout.chart,
|
|
2939
|
+
descriptor: layout.descriptor,
|
|
2165
2940
|
plot: layout.plot,
|
|
2166
2941
|
start: this.rectangleZoomRect.start,
|
|
2167
2942
|
end: this.rectangleZoomRect.end,
|
|
@@ -2188,11 +2963,34 @@ var ChartGridController = class {
|
|
|
2188
2963
|
const inPlot = point.x >= layout.plot.x && point.x <= layout.plot.x + layout.plot.width && point.y >= layout.plot.y && point.y <= layout.plot.y + layout.plot.height;
|
|
2189
2964
|
if (!inPlot) return;
|
|
2190
2965
|
event.preventDefault();
|
|
2966
|
+
if (event.shiftKey) {
|
|
2967
|
+
const wheelDelta = event.deltaY || event.deltaX;
|
|
2968
|
+
const currentScale = this.yScales.get(layout.chart.id) || 1;
|
|
2969
|
+
this.yScales.set(
|
|
2970
|
+
layout.chart.id,
|
|
2971
|
+
scaleValueRange(
|
|
2972
|
+
currentScale,
|
|
2973
|
+
wheelDelta,
|
|
2974
|
+
WHEEL_SCALE_SENSITIVITY
|
|
2975
|
+
)
|
|
2976
|
+
);
|
|
2977
|
+
this.requestRender();
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2191
2980
|
const state = this.viewStates.get(layout.chart.id);
|
|
2192
|
-
const
|
|
2981
|
+
const range = this.getRange(layout, state);
|
|
2982
|
+
const ratio = clamp(
|
|
2983
|
+
this.getTransform(layout, state, range).categoryRatio(point),
|
|
2984
|
+
0,
|
|
2985
|
+
1
|
|
2986
|
+
);
|
|
2193
2987
|
const anchor = state.xMin + ratio * (state.xMax - state.xMin);
|
|
2194
2988
|
const zoom = Math.exp(event.deltaY * 1e-3);
|
|
2195
|
-
const span = clamp(
|
|
2989
|
+
const span = clamp(
|
|
2990
|
+
(state.xMax - state.xMin) * zoom,
|
|
2991
|
+
getMinimumCategorySpan(layout.chart),
|
|
2992
|
+
X_SCALE_MAX_SPAN
|
|
2993
|
+
);
|
|
2196
2994
|
this.viewStates.set(layout.chart.id, { xMin: anchor - ratio * span, xMax: anchor + (1 - ratio) * span });
|
|
2197
2995
|
this.requestRender();
|
|
2198
2996
|
}
|
|
@@ -2202,32 +3000,55 @@ var ChartGridController = class {
|
|
|
2202
3000
|
event.preventDefault();
|
|
2203
3001
|
const { layout, point } = context;
|
|
2204
3002
|
const inPlot = point.x >= layout.plot.x && point.x <= layout.plot.x + layout.plot.width && point.y >= layout.plot.y && point.y <= layout.plot.y + layout.plot.height;
|
|
2205
|
-
const data = inPlot ? screenPointToDataPoint({ point, chart: layout.chart, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } }) : null;
|
|
3003
|
+
const data = inPlot ? screenPointToDataPoint({ point, chart: layout.chart, descriptor: layout.descriptor, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } }) : null;
|
|
2206
3004
|
this.options.onChartContextMenu({ chart: layout.chart, event, point: data });
|
|
2207
3005
|
}
|
|
2208
3006
|
updateCrosshair(point, layout) {
|
|
2209
3007
|
const inPlot = point.x >= layout.plot.x && point.x <= layout.plot.x + layout.plot.width && point.y >= layout.plot.y && point.y <= layout.plot.y + layout.plot.height;
|
|
2210
3008
|
if (!inPlot || !this.options.showTooltips) return this.clearCrosshair();
|
|
2211
3009
|
const state = this.viewStates.get(layout.chart.id);
|
|
2212
|
-
const
|
|
2213
|
-
const range =
|
|
2214
|
-
const
|
|
2215
|
-
|
|
3010
|
+
const orientation = layout.descriptor.orientation;
|
|
3011
|
+
const range = this.getRange(layout, state);
|
|
3012
|
+
const transform = this.getTransform(layout, state, range);
|
|
3013
|
+
const xValue = transform.screenToData(point).x;
|
|
3014
|
+
const tooltipSeries = getOrderedSeries(
|
|
3015
|
+
layout.chart,
|
|
3016
|
+
this.options.seriesOrderByChart
|
|
3017
|
+
);
|
|
3018
|
+
const points = tooltipSeries.map((series, seriesIndex) => {
|
|
3019
|
+
const visible = series.getVisiblePoints(state.xMin, state.xMax, transform.categoryPixelLength);
|
|
2216
3020
|
if (!visible.pointCount) return null;
|
|
2217
3021
|
const index = getNearestPointIndex(visible.x, xValue);
|
|
2218
|
-
const
|
|
2219
|
-
|
|
2220
|
-
|
|
3022
|
+
const category = this.surface.renderers.getTooltipCategory(
|
|
3023
|
+
layout.descriptor.rendererType,
|
|
3024
|
+
{
|
|
3025
|
+
visiblePoints: visible,
|
|
3026
|
+
pointIndex: index,
|
|
3027
|
+
seriesIndex,
|
|
3028
|
+
seriesCount: tooltipSeries.length,
|
|
3029
|
+
categoryMin: state.xMin,
|
|
3030
|
+
categoryMax: state.xMax
|
|
3031
|
+
}
|
|
3032
|
+
);
|
|
3033
|
+
const data = { x: category, y: visible.y[index] };
|
|
3034
|
+
const screen = transform.dataToScreen(data);
|
|
3035
|
+
return { id: series.id, name: series.name, color: series.color, x: screen.x, y: screen.y, xValue: visible.x[index], yValue: data.y };
|
|
2221
3036
|
}).filter(Boolean);
|
|
2222
3037
|
if (!points.length) return this.clearCrosshair();
|
|
2223
|
-
const nearest = points.reduce((best, item) =>
|
|
3038
|
+
const nearest = points.reduce((best, item) => {
|
|
3039
|
+
const itemDistance = orientation === "horizontal" ? Math.abs(item.y - point.y) : Math.abs(item.x - point.x);
|
|
3040
|
+
const bestDistance = orientation === "horizontal" ? Math.abs(best.y - point.y) : Math.abs(best.x - point.x);
|
|
3041
|
+
return itemDistance < bestDistance ? item : best;
|
|
3042
|
+
});
|
|
2224
3043
|
this.crosshair = {
|
|
3044
|
+
chartId: layout.chart.id,
|
|
2225
3045
|
x: point.x,
|
|
2226
3046
|
y: point.y,
|
|
2227
3047
|
xValue: nearest.xValue,
|
|
3048
|
+
categoryLabel: getCategoryLabel(layout.chart, nearest.xValue),
|
|
2228
3049
|
points,
|
|
2229
3050
|
tooltipX: point.x + 232 > layout.rect.x + layout.rect.width ? point.x - 232 : point.x + 12,
|
|
2230
|
-
tooltipY: clamp(point.y + 12, layout.
|
|
3051
|
+
tooltipY: clamp(point.y + 12, layout.plot.y, layout.rect.y + layout.rect.height - 96)
|
|
2231
3052
|
};
|
|
2232
3053
|
this.requestRender();
|
|
2233
3054
|
}
|
|
@@ -2273,6 +3094,8 @@ var ChartGridController = class {
|
|
|
2273
3094
|
}
|
|
2274
3095
|
toggleMovingAverage(chartId) {
|
|
2275
3096
|
if (!chartId || this.options.disableDrawings) return;
|
|
3097
|
+
const chart = this.options.charts.find((item) => item.id === chartId);
|
|
3098
|
+
if (!chart || !this.chartDescriptors.get(chartId)?.capabilities.movingAverage) return;
|
|
2276
3099
|
const current = this.movingAverageByChart[chartId] || { period: 21, type: "ema", hideBase: false };
|
|
2277
3100
|
this.movingAverageByChart = { ...this.movingAverageByChart, [chartId]: { ...current, enabled: !current.enabled } };
|
|
2278
3101
|
this.emit("onMovingAverageToggle", chartId);
|
|
@@ -2300,7 +3123,7 @@ var ChartGridController = class {
|
|
|
2300
3123
|
const overlay = document.createElement("div");
|
|
2301
3124
|
overlay.dataset.alienchartsFullscreen = "";
|
|
2302
3125
|
overlay.className = "aliencharts-root fixed inset-0 z-[60] bg-background p-2";
|
|
2303
|
-
overlay.innerHTML = `<canvas class="pointer-events-none absolute left-0 top-0 z-10 block"></canvas><div data-fullscreen-cell>${chartCellHtml(chart, 0, this.options.backgroundColor)}</div><div data-fullscreen-overlays class="pointer-events-none absolute left-0 top-0 z-20" style="width:100%;height:100%"></div>`;
|
|
3126
|
+
overlay.innerHTML = `<canvas class="pointer-events-none absolute left-0 top-0 z-10 block"></canvas><div data-fullscreen-cell>${chartCellHtml(chart, this.chartDescriptors.get(chart.id), 0, this.options.backgroundColor)}</div><div data-fullscreen-overlays class="pointer-events-none absolute left-0 top-0 z-20" style="width:100%;height:100%"></div>`;
|
|
2304
3127
|
document.body.append(overlay);
|
|
2305
3128
|
this.fullscreenOverlay = overlay;
|
|
2306
3129
|
this.fullscreenNode = overlay.querySelector("[data-chart-index]");
|
|
@@ -2338,6 +3161,8 @@ var ChartGridController = class {
|
|
|
2338
3161
|
const editable = event.target instanceof HTMLElement && (event.target.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(event.target.tagName));
|
|
2339
3162
|
if (editable) return;
|
|
2340
3163
|
const chartId = this.fullscreenChartId || this.focusedChartId;
|
|
3164
|
+
const chart = this.options.charts.find((item) => item.id === chartId);
|
|
3165
|
+
const capabilities = chart && this.chartDescriptors.get(chartId)?.capabilities;
|
|
2341
3166
|
const key = event.key.toLowerCase();
|
|
2342
3167
|
if (["arrowleft", "arrowright", "arrowup", "arrowdown"].includes(key) && !this.fullscreenChartId) {
|
|
2343
3168
|
this.moveFocus(key.replace("arrow", ""));
|
|
@@ -2351,10 +3176,10 @@ var ChartGridController = class {
|
|
|
2351
3176
|
this.rectangleZoomChartId = this.rectangleZoomChartId === chartId ? null : chartId;
|
|
2352
3177
|
event.preventDefault();
|
|
2353
3178
|
this.requestRender();
|
|
2354
|
-
} else if (key === "m") {
|
|
3179
|
+
} else if (key === "m" && capabilities?.movingAverage) {
|
|
2355
3180
|
this.toggleMovingAverage(chartId);
|
|
2356
3181
|
event.preventDefault();
|
|
2357
|
-
} else if (["t", "h", "v", "p"].includes(key)) {
|
|
3182
|
+
} else if (["t", "h", "v", "p"].includes(key) && capabilities?.drawings) {
|
|
2358
3183
|
this.toggleDrawingTool({ t: "trendline", h: "hline", v: "vline", p: "pin" }[key]);
|
|
2359
3184
|
event.preventDefault();
|
|
2360
3185
|
} else if ((key === "delete" || key === "backspace") && this.selectedDrawingId) {
|
|
@@ -2502,7 +3327,7 @@ var makeSeries = ({ chartIndex, seriesIndex, pointCount }) => {
|
|
|
2502
3327
|
value += Math.sin(i * waveA + chartIndex) * amplitudeA + Math.cos(i * waveB + seriesIndex) * amplitudeB + Math.sin(i * waveC) * amplitudeA * 0.35 + trend + (random() - 0.5) * noise + spike;
|
|
2503
3328
|
y[i] = value;
|
|
2504
3329
|
}
|
|
2505
|
-
return
|
|
3330
|
+
return createLineSeries({
|
|
2506
3331
|
id: `chart-${chartIndex}-series-${seriesIndex}`,
|
|
2507
3332
|
name: `Run ${seriesIndex + 1}`,
|
|
2508
3333
|
color: palette[(chartIndex + seriesIndex) % palette.length],
|
|
@@ -2525,8 +3350,11 @@ var createMockCharts = ({
|
|
|
2525
3350
|
}));
|
|
2526
3351
|
};
|
|
2527
3352
|
export {
|
|
3353
|
+
BarSeries,
|
|
2528
3354
|
ChartGrid,
|
|
2529
3355
|
LineSeries,
|
|
3356
|
+
createBarSeries,
|
|
3357
|
+
createLineSeries,
|
|
2530
3358
|
createMockCharts,
|
|
2531
3359
|
createSeries
|
|
2532
3360
|
};
|