Rendimiento
Un pipeline Canvas2D de dos canvas solo vuelve a pintar lo que ha cambiado, y
cada paso por fotograma depende de lo que hay en pantalla, no de cuánto
historial está cargado. Las cifras de abajo proceden de pnpm bench (un solo núcleo) y del perfilado del widget en vivo; el Feature Lab mide los cambios reales a
medida que haces clic.
Fotogramas: estables de 500 a 100,000 barras
- Dos canvas: un canvas de escena (cuadrícula, series, indicadores, objetos del gráfico, ejes) y un canvas superior ligero para la cruz, la leyenda y demás elementos ligados al puntero. Al pasar el cursor solo se vuelve a pintar el canvas superior (~0.2 ms), y el navegador compone dos superficies, no cuatro.
- Renderizado del rango visible: todos los renderizadores, la escala automática y el rango de precios de los indicadores recorren solo las barras a la vista.
- Sin basura por fotograma: las instantáneas de la vista se guardan en caché entre cambios; los formateadores de números y fechas se reutilizan en lugar de recrearse para cada etiqueta.
- Eje de precio de tamaño automático: ajustar el eje a su etiqueta más larga cuesta ~0.05 ms y solo se recalcula el diseño cuando el ancho cambia de verdad.
Ticks en vivo: indicadores incrementales
Un tick solo mueve la barra en formación, así que los indicadores que
implementan update() recalculan únicamente esa barra (SMA, EMA, WMA,
VWMA, Bollinger, Envelope, RSI, MACD, ATR, OBV, Stochastic). Los demás recurren a
un recálculo completo. BB + EMA + RSI + MACD:
| Historial | Recálculo completo | update() incremental |
|---|---|---|
| 20,000 barras | ~5 ms | ~0.0005 ms |
| 100,000 barras | ~27 ms | ~0.001 ms |
Los indicadores personalizados también pueden aprovecharlo; consulta Plugins → actualizaciones rápidas en vivo.
Cambio de símbolo o de temporalidad
- Cargas completas baratas: los valores de los indicadores viven en un
IndicatorValueMap(basado en arrays, ~3× más barato de construir que unMapindexado por marca de tiempo), ysetDatareutiliza las barras que ya son válidas en lugar de copiarlas. - Indicador de carga solo cuando es lento: el gráfico anterior sigue visible; solo aparece un velo si un cambio dura más de 200 ms, así que un cambio de red normal de ~130 ms nunca parpadea.
- Sin datos obsoletos: las solicitudes de historial superadas se descartan y los sockets antiguos se desconectan, así que el último clic siempre gana.
- Remuestreo local: con datos estáticos se cambia de temporalidad sin volver a descargar; los remuestreos lentos pintan el velo antes de que el hilo principal se ocupe.
Submuestreo LTTB
Los gráficos de línea y de área submuestrean el rango visible a ~2 puntos por píxel con Largest-Triangle-Three-Buckets cuando hay muchas más barras que píxeles: la línea se ve idéntica mientras se dibujan decenas de veces menos puntos. Con el zoom normal no hace nada. El algoritmo se exporta para que lo uses tú también:
import { lttbDownsample } from '@tradecanvas/chart'
// indices preserving the shape of a 100k series, reduced to 1600 points
const idx = lttbDownsample(series.length, 1600, (i) => series[i].close) | Puntos visibles → 1600 | Tiempo / fotograma | Rendimiento |
|---|---|---|
| 10,000 | ~0.025 ms | 39,600 / s |
| 100,000 | ~0.32 ms | 3,100 / s |
| 1,000,000 | ~2.6 ms | 380 / s |
Indicadores con zoom alejado
Cuando cada barra mide menos de un píxel, las líneas, bandas e histogramas de los indicadores se dibujan con un tramo por columna de píxeles: del punto más bajo al más alto de la columna, unido a la columna anterior y tan ancho como la línea. Se ve casi igual que un trazo por miles de puntos con una fracción del trabajo de rasterizado. Con el zoom alejado sobre 200.000 barras con Bandas de Bollinger, EMA, RSI y MACD, un fotograma pasó de unos 54 ms a unos 21 ms en una GPU integrada. node scripts/bench-render.mjs mide estas cifras en tu propio equipo.
Renderizador WebGL (versión preliminar)
renderer: 'webgl' dibuja el área del gráfico y los paneles de indicadores con WebGL 2, en un canvas bajo la escena 2D: la cuadrícula, las sesiones, las velas y el volumen directamente, y el dibujo en Canvas 2D de los indicadores, las líneas de comparación y la mayoría de los tipos de gráfico se graba como trazos, rellenos y rectángulos de la GPU, con los bordes suavizados como en Canvas 2D. El texto, los dibujos, las órdenes, los ejes y la cruceta siguen en Canvas 2D, igual que todo lo que la GPU no dibujaría igual (se deja a Canvas 2D en orden, así que el apilado no cambia); los plugins de indicadores propios funcionan sin cambios. Las velas coinciden con Canvas 2D con una diferencia máxima de 2/255; las líneas y los rellenos solo difieren en algunos píxeles de borde suavizados. El código WebGL es un chunk propio (unos 17 KB con gzip) que se carga la primera vez que se usa; donde falta WebGL 2, o si se pierde el contexto, el gráfico sigue dibujando con Canvas 2D.
const chart = new Chart(el, { renderer: 'webgl' })
chart.on('rendererChange', (e) => console.log(e.payload))
// { renderer: 'webgl' }, or { renderer: 'canvas', reason: 'unsupported' | 'contextLost' }
await chart.setRenderer('canvas') // resolves to what draws now Tiempo por fotograma al desplazar, en una GPU integrada (Intel UHD; 16.7 ms son 60 fps):
| Gráfico | Densidad de píxeles | Canvas 2D | WebGL |
|---|---|---|---|
| 1600×900, 500 velas + 4 indicadores | 2 | 27.4 ms | 19.6 ms |
| 1600×900, zoom alejado sobre 200,000 barras + 4 indicadores | 2 | 34.5 ms | 20.2 ms |
| Seis gráficos, cada uno con 500 velas y dos indicadores | 2 | 23.5 ms | 17.2 ms |
| 2560×1400, 2,000 velas + 4 indicadores | 1 | 41.6 ms | 17.7 ms |
| 2560×1400, 2,000 velas + 4 indicadores | 1.5 | 70.8 ms | 17.6 ms |
| 2560×1400, 2,000 velas + 4 indicadores | 2 | 114.5 ms | 29.1 ms |
| 2560×1400, 2,000 velas | 2 | 33.1 ms | 20.9 ms |
La mayoría de los fotogramas WebGL de arriba se quedan en 16.7 ms; las medias incluyen algunos más largos. Con densidad 2 en un gráfico de 2560×1400, solo la composición de las capas a tamaño completo ya le cuesta al navegador unos 23 ms en esta GPU. node scripts/bench-render.mjs --renderer=webgl mide estas cifras en tu equipo.
Fuera del hilo principal
IndicatorWorkerHost ejecuta los cálculos de los indicadores en un
Web Worker con un calculate() basado en Promise, un tiempo de
espera por solicitud y una alternativa síncrona para SSR y tests.