UNPKG

@teaui/core

Version:

A high-level terminal UI library for Node

109 lines 3.54 kB
import { interpolate } from '../../geometry.js'; import { Canvas } from '../Canvas.js'; import { Chart } from './Chart.js'; export class LineChart extends Chart { #extract; #xLabelsFn; #yLabelsFn; #canvas = new Canvas(); constructor(data, props) { const { extract, xLabels, yLabels, ...rest } = props; super(data, rest); this.#extract = extract; this.#xLabelsFn = xLabels; this.#yLabelsFn = yLabels; } getXRange() { if (this.data.length === 0) return { min: 0, max: 1 }; let min = Infinity; let max = -Infinity; for (const row of this.data) { const [x] = this.#extract(row); if (x < min) min = x; if (x > max) max = x; } if (min === max) return { min: min - 1, max: max + 1 }; return { min, max }; } getYRange() { if (this.data.length === 0) return { min: 0, max: 1 }; let min = Infinity; let max = -Infinity; for (const row of this.data) { const [, y] = this.#extract(row); if (y < min) min = y; if (y > max) max = y; } if (min === max) return { min: min - 1, max: max + 1 }; return { min, max }; } getXLabels() { if (!this.#xLabelsFn) return []; return this.data.map(this.#xLabelsFn); } getYLabels(count) { if (!this.#yLabelsFn) { return defaultYLabels(this.getYRange(), count); } const range = this.getYRange(); const labels = []; for (let i = 0; i < count; i++) { const value = interpolate(i, [0, count - 1], [range.max, range.min]); labels.push(this.#yLabelsFn(value)); } return labels; } renderChart(viewport, layout) { if (viewport.isEmpty || this.data.length === 0) return; const extract = this.#extract; this.#canvas.withContext(layout.width, layout.height, canvas => { const pixelW = canvas.pixelWidth; const pixelH = canvas.pixelHeight; let prevPx; let prevPy; for (const row of this.data) { const [x, y] = extract(row); const px = Math.round(interpolate(x, [layout.xRange.min, layout.xRange.max], [0, pixelW - 1], true)); // Y is inverted: top of screen = min pixel Y = max data Y const py = Math.round(interpolate(y, [layout.yRange.min, layout.yRange.max], [pixelH - 1, 0], true)); if (prevPx !== undefined && prevPy !== undefined) { canvas.line(prevPx, prevPy, px, py); } else { canvas.set(px, py); } prevPx = px; prevPy = py; } }); this.#canvas.render(viewport); } } function defaultYLabels(range, count) { const labels = []; for (let i = 0; i < count; i++) { const value = interpolate(i, [0, count - 1], [range.max, range.min]); labels.push(formatNumber(value)); } return labels; } function formatNumber(n) { if (Number.isInteger(n)) return String(n); if (Math.abs(n) >= 100) return String(Math.round(n)); if (Math.abs(n) >= 10) return n.toFixed(1); return n.toFixed(2); } //# sourceMappingURL=LineChart.js.map