Solución de problemas

Comprobaciones y correcciones copiables para fallos comunes en la integración de gráficos.

Estos modos de fallo se generan a partir del mismo conocimiento versionado usado por diagnose_chart. Comience por el síntoma que más se parezca a la aplicación, aplique la comprobación diagnóstica y conserve solo las correcciones que preserven el ciclo de vida documentado y el contrato de propiedad de typed-array.

Si ninguna entrada coincide, inspeccione el símbolo relevante en la referencia de API anidada o pida al servidor MCP local que ejecute un diagnóstico enfocado. Reduzca la reproducción a renderMode: "main" y animated: false solo como paso diagnóstico, no como configuración de producción automática.

Índice de síntomas

El lienzo está en blanco o tiene tamaño cero

Se aplica a: line, stock.

Síntoma. El gráfico se inicializa sin error visible, pero no aparece ninguna trama o el lienzo mide cero píxeles de alto.

Causa probable. El contenedor del lienzo no tenía una altura concreta cuando se inicializó el renderizador.

Comprobar

  • Inspeccione el host y el lienzo con getBoundingClientRect(); tanto el ancho como la altura deben ser mayores que cero.
  • Compruebe si un ancestro en flex o grid colapsó la fila del gráfico.

Corregir

  • Dé al host una altura concreta o min-height antes de que initialize() se ejecute.
  • Haga que el lienzo llene ese host en lugar de confiar en las dimensiones intrínsecas del lienzo.

Dé al gráfico un host medible

.chart-host {
  position: relative;
  width: 100%;
  height: clamp(20rem, 55vh, 34rem);
}

.chart-host > canvas {
  display: block;
  width: 100%;
  height: 100%;
}

Los datos se instalan antes de que el gráfico esté listo

Se aplica a: line, stock.

Síntoma. El primer conjunto de datos no aparece, mientras que una actualización posterior o un remount funcionan.

Causa probable. Una integración vanilla llamó a un método de datos antes de que initialize() terminara, o inicializó el mismo lienzo más de una vez.

Comprobar

  • Verifique que initialize() se espere exactamente una vez por cada instancia de gráfico.
  • Si un framework gestiona la vista, prefiera el adaptador oficial para que el orden de montaje y desmontaje se gestione por usted.

Corregir

  • Espere a initialize() antes de llamar a setData() o setMultiSeriesData().
  • Destruya el gráfico antiguo antes de reutilizar su lienzo para otra instancia.

Use el ciclo de vida vanilla en orden

const chart = new LineChart(canvas, { renderMode: "auto" });
await chart.initialize();
chart.setData(data);

// Call this when the view is permanently removed.
export function disposeChart() {
  chart.destroy();
}

El renderizado en worker revierte al main thread

Aplica a: line, stock.

Síntoma. Un gráfico funciona pero se resuelve a renderizado en el main thread, o la construcción del worker falla solo en producción.

Causa probable. El worker, OffscreenCanvas, transferControlToOffscreen, o la Content Security Policy desplegada impidieron que el renderer en worker arrancara.

Comprobar

  • Usa setStatsCallback() temporalmente e inspecciona las estadísticas.renderMode.
  • Revisa la consola del navegador y la directiva worker-src de la Content Security Policy.

Solucionar

  • Mantén renderMode en auto salvo que forzar un modo sea necesario para el diagnóstico.
  • Permite la URL del worker generada por la aplicación en worker-src y conserva el fallback al main thread operativo.

Confirmar el renderer resuelto

chart.setStatsCallback((stats) => {
  console.info("Sixtyfold renderer:", stats.renderMode);
  chart.setStatsCallback(null);
});

Los typed arrays quedan detached después de setData

Aplica a: line, stock.

Síntoma. Un typed array fuente tiene byteLength 0 o ya no puede leerse después de que los datos se pasaron a un gráfico respaldado por worker.

Causa probable. Los datos masivos del gráfico se transfieren al renderer en modo worker; los ArrayBuffers transferidos abandonan el llamador.

Comprobar

  • Inspecciona byteLength inmediatamente antes y después de la llamada de datos masivos.
  • Confirma si otro subsistema necesita genuinamente conservar una copia legible.

Solucionar

  • Trata la llamada de datos masivos como una transferencia de propiedad.
  • Clona solo las columnas que otro subsistema deba retener; evita duplicar conjuntos de datos de millones de valores por defecto.

Conservar una copia intencionada en la aplicación

const retainedX = sourceX.slice();
const retainedY = sourceY.slice();

chart.setData({
  x: sourceX,
  y: sourceY,
  length: sourceX.length,
});

// retainedX and retainedY remain readable.

El eje temporal está comprimido, vacío o muy en el futuro

Aplica a: line, stock.

Síntoma. Las marcas muestran fechas implausibles, el rango visible colapsa o el paneo se comporta de forma impredecible.

Causa probable. La columna X usó segundos o cadenas en lugar de milisegundos epoch finitos ordenados, o las observaciones no estaban ordenadas.

Comprobar

  • Inspecciona las marcas temporales primera y última y conviértelas con new Date(value).
  • Verifica que cada marca temporal sea finita y no decreciente.

Solucionar

  • Convierte segundos epoch a milisegundos antes de crear el Float64Array.
  • Parsea fechas ISO una vez durante la ingestión y ordena observaciones completas antes de dividirlas en columnas.

Normaliza las marcas temporales durante la ingestión

const timestamp = Float64Array.from(rows, (row) => {
  const value = Date.parse(row.recordedAt);
  if (!Number.isFinite(value)) throw new TypeError("Invalid recordedAt value");
  return value;
});

Las columnas tienen longitudes diferentes o valores numéricos inválidos

Aplica a: line, stock.

Síntoma. El gráfico rechaza un conjunto de datos, trunca inesperadamente o renderiza valores bajo marcas temporales equivocadas.

Causa probable. Los datos columnars se crearon a partir de filtros o mapeos separados, por lo que las columnas de tiempo y valor ya no describen las mismas observaciones.

Comprobar

  • Compara la longitud de cada columna antes de llamar al gráfico.
  • Valida la conversión numérica antes de transferir los buffers.

Corregir

  • Filtrar y ordenar primero las observaciones completas de la fuente, luego dividirlas en columnas tipadas alineadas.
  • Usar NaN explícito solo para una brecha de serie de líneas documentada; los valores OHLCV deben seguir siendo velas válidas.

Rechazar columnas desalineadas antes de la transferencia

const lengths = [timestamp.length, open.length, high.length, low.length, close.length, volume.length];
if (!lengths.every((length) => length === lengths[0])) {
  throw new RangeError(`OHLCV columns are misaligned: ${lengths.join(", ")}`);
}

SSR o hidratación accede a APIs de gráficos exclusivas del navegador

Se aplica a: line, stock.

Síntoma. Un render en servidor falla porque window, document, Worker o HTMLCanvasElement no está disponible, o la hidratación produce un árbol diferente.

Causa probable. El gráfico interactivo del navegador se construyó durante la evaluación en servidor en lugar de después del montaje en cliente.

Comprobar

  • Buscar la construcción del gráfico en el ámbito del módulo o dentro de un componente de servidor.
  • Separar la generación de imagen en servidor mediante @sixtyfold/ssr de la interacción en navegador.

Corregir

  • Usar el adaptador oficial del framework dentro de un componente propiedad del cliente.
  • Renderizar un host estable durante SSR e inicializar el gráfico interactivo solo tras el montaje.

Mantener un gráfico Next.js detrás del límite del cliente

"use client";

import type { MultiSeriesData, TimeSeriesData } from "@sixtyfold/core";
import { SixtyfoldLineChart } from "@sixtyfold/react/line";

type LineData = TimeSeriesData | MultiSeriesData;

export function ChartPanel({ data }: { data: LineData }) {
  return <SixtyfoldLineChart data={data} options={{ renderMode: "auto" }} />;
}

Permanecen workers o memoria tras la navegación

Se aplica a: line, stock.

Síntoma. La memoria crece tras abrir repetidamente la vista, o los gráficos antiguos siguen renderizando tras la navegación.

Causa probable. Un gráfico vanilla o un worker temporal de datos no se destruyó cuando se eliminó la vista propietaria.

Comprobar

  • Registrar una instantánea del heap del navegador antes y después de varios ciclos mount/unmount.
  • Buscar instancias de gráficos retenidas, workers, observadores o arrays de fuente decodificados.

Corregir

  • Llamar a destroy() exactamente una vez por cada instancia de gráfico vanilla.
  • Terminar workers temporales fetch/decode tras su última transferencia; los adaptadores oficiales del framework destruyen automáticamente los workers del gráfico.

Liberar workers del gráfico y de ingestión

return () => {
  dataWorker?.terminate();
  chart.destroy();
};

La interacción es lenta o pierde frames

Se aplica a: line, stock.

Síntoma. El zoom, paneo, decodificación inicial o las actualizaciones por streaming bloquean visiblemente o no alcanzan la tasa de frames objetivo.

Causa probable. El main thread está decodificando o remodelando demasiados datos, el gráfico resolvió al renderer de reserva, o la carga de trabajo visible incluye más pasadas de dibujo de las que el dispositivo objetivo puede sostener.

Comprobar

  • Inspeccionar estadísticas.renderMode, frameTime, trabajo visible y disponibilidad de LOD durante la secuencia de interacción real.
  • Perfilar fetch, decodificación, conversión, transferencia y renderizado por separado.

Corregir

  • Usar renderMode auto, preparar arrays tipados columnarios en un worker de datos dedicado, transferir una vez y terminar ese worker de ingestión.
  • Benchmark de navegadores representativos, DPRs, recuentos de series y gestos; 60 FPS es un objetivo de diseño y no una garantía independiente del dispositivo.

Medir la vía de renderizado activa

chart.setStatsCallback((stats) => {
  console.table({
    mode: stats.renderMode,
    fps: stats.fps,
    frameTime: stats.frameTime,
    ready: stats.lodReady,
  });
}, { intervalMs: 250 });

El detalle de la línea cambia bruscamente tras estabilizar el zoom

Se aplica a: line.

Síntoma. La vista se anima suavemente pero la textura de la línea cambia de nuevo al final del gesto.

Causa probable. La densidad de presentación o el rebasing de la cuadrícula es demasiado agresivo para la carga de trabajo, o el código de la aplicación sustituye los datos o la apariencia tras finalizar la animación.

Comprobar

  • Reproduce la misma secuencia de vista mientras observas la densidad de presentación, el delta de la cuadrícula y las visitas a las consultas.
  • Comprueba si la aplicación llama a setData(), setAppearance() o restaura la vista después de que la animación se estabilice.

Solucionar

  • Empieza con modo adaptativo, densidad 0.75, rebaseRatio 1.25 y quantizationStep 0.25.
  • Usa setLODOptions() para ajustar un coeficiente a la vez; usa densidad 0.25 para rellenos de rango costosos o trabajo multiserie excepcionalmente grande.

Ajustar la presentación sin recrear el gráfico

chart.setLODOptions({
  mode: "adaptive",
  density: 0.75,
  rebaseRatio: 1.25,
  quantizationStep: 0.25,
});

Una línea conecta a través de observaciones ausentes

Se aplica a: line.

Síntoma. Una línea cruza visualmente un intervalo donde no existe ninguna medición.

Causa probable. Las observaciones faltantes se eliminaron o se reemplazaron por cero en lugar de representarse como una brecha NaN alineada.

Comprobar

  • Confirma que la columna X compartida incluye el intervalo faltante cuando se requiere una ruptura visible.
  • Inspecciona si la ingesta convirtió valores null en cero.

Solucionar

  • Mantén las posiciones X alineadas y codifica los valores de línea faltantes como Number.NaN.
  • No sintetices velas OHLCV ni conectes cierres de mercado; el comportamiento en escala temporal de acciones se rige por las velas observadas.

Representar una brecha real en una serie de líneas

const data = {
  x: new Float64Array([0, 1, 2, 3]),
  y: new Float64Array([12, Number.NaN, Number.NaN, 18]),
  length: 4,
};