import { buildCsvDownloadItem, buildPngDownloadItem, type DownloadItem, } from '../actions/download' import type { TimeseriesWidgetData } from './types' /** * Download menu items for the Timeseries widget. Always includes a CSV * item with `time, series_1, series_2, …` columns (one row per unique time * across all series; ISO-8601 strings for `Date`/numeric times). When * `getCaptureEl` is supplied, prepends a PNG item that rasterises the * captured element via `html2canvas`. */ export function createTimeseriesDownloadConfig(args: { filename: string getData: () => TimeseriesWidgetData seriesNames?: readonly string[] getCaptureEl?: () => HTMLElement | null pngPixelRatio?: number pngBackgroundColor?: string | null }): DownloadItem[] { const items: DownloadItem[] = [] if (args.getCaptureEl) { items.push( buildPngDownloadItem({ filename: args.filename, getCaptureEl: args.getCaptureEl, pixelRatio: args.pngPixelRatio, backgroundColor: args.pngBackgroundColor, }), ) } items.push( buildCsvDownloadItem({ filename: args.filename, getRows: () => { const data = args.getData() const seriesCount = data.length // Collect every unique time, preserving insertion order. const timeKeys: (Date | number | string)[] = [] const seenKeys = new Set() for (const series of data) { for (const point of series) { const key = String(point.name) if (!seenKeys.has(key)) { seenKeys.add(key) timeKeys.push(point.name) } } } // Build a quick lookup per series for O(rows × series) emit. const lookups = data.map( (series) => new Map(series.map((p) => [String(p.name), p.value])), ) const header: unknown[] = ['time'] for (let i = 0; i < seriesCount; i++) { header.push(args.seriesNames?.[i] ?? `series_${i + 1}`) } const rows: unknown[][] = [header] for (const key of timeKeys) { const row: unknown[] = [formatTime(key)] const lookupKey = String(key) for (const lookup of lookups) row.push(lookup.get(lookupKey) ?? '') rows.push(row) } return rows }, }), ) return items } function formatTime(v: Date | number | string): string { if (v instanceof Date) return v.toISOString() if (typeof v === 'number') return new Date(v).toISOString() return v }