import { createSeriesHighlight } from "./highlight.js"; import { createSeriesLoader } from "./loader.js"; import { renderPlot } from "./plot.js"; import { createScrubber } from "./scrubber/index.js"; import { createSvgElement } from "./svg.js"; import { getViewBoxHeight, VIEWBOX_WIDTH } from "./viewbox.js"; /** * @param {Object} args * @param {SVGSVGElement} args.svg * @param {LegendReadout} args.readout * @param {HTMLElement} args.menu * @param {HTMLElement[]} args.items * @param {HTMLElement} args.status * @param {Chart} args.chart * @param {() => ChartView} args.getView * @param {() => ChartScale} args.getScale * @param {() => ChartOrder} args.getOrder * @param {() => TimeframeValue} args.getTimeframe */ export function createChartRenderer({ svg, readout, menu, items, status, chart, getView, getScale, getOrder, getTimeframe, }) { const group = createSvgElement("g"); const highlight = createSeriesHighlight(items, menu); const loadSeries = createSeriesLoader(chart); /** @type {LoadedSeries[]} */ let loadedSeries = []; /** @type {ReturnType | undefined} */ let scrubber; const resizeObserver = new ResizeObserver(renderCurrent); let active = false; let loadId = 0; svg.append(group); function clearStatus() { status.textContent = ""; svg.removeAttribute("aria-busy"); } /** @param {string} message */ function setStatus(message) { status.textContent = message; } function renderCurrent() { if (!active || !loadedSeries.length) return; const height = getViewBoxHeight(svg); svg.setAttribute("viewBox", `0 0 ${VIEWBOX_WIDTH} ${height}`); group.replaceChildren(); highlight.clearNodes(); scrubber ??= createScrubber(svg, readout, highlight, chart.unit.format); scrubber.setSeries( renderPlot(getView(), { group, loadedSeries, height, highlight, scale: getScale(), order: getOrder(), }), height, ); } async function loadCurrent() { const id = (loadId += 1); const loadingTimer = setTimeout(() => { if (id === loadId && active) setStatus("Loading"); }, 250); setStatus(""); svg.setAttribute("aria-busy", "true"); try { const nextSeries = await loadSeries(getTimeframe()); if (id !== loadId || !active) return; loadedSeries = nextSeries; renderCurrent(); clearStatus(); } catch (error) { if (id !== loadId) return; console.error(error); setStatus("Chart unavailable"); } finally { clearTimeout(loadingTimer); if (id === loadId) svg.removeAttribute("aria-busy"); } } function resume() { if (active) return; active = true; resizeObserver.observe(svg); void loadCurrent(); } function suspend() { if (!active) return; active = false; loadedSeries = []; loadId += 1; resizeObserver.disconnect(); group.replaceChildren(); highlight.clearNodes(); scrubber?.clear(); clearStatus(); } return { loadCurrent, renderCurrent, resume, suspend, }; }