@teaui/core
Version:
A high-level terminal UI library for Node
109 lines • 3.54 kB
JavaScript
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