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