simple-ascii-chart
Version:
Simple ascii chart generator
1,530 lines (1,521 loc) • 40.2 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
AXIS: () => AXIS,
CHART: () => CHART,
EMPTY: () => EMPTY,
LAYOUT: () => LAYOUT,
POINT: () => POINT,
THRESHOLDS: () => THRESHOLDS,
default: () => index_default,
plot: () => plot
});
module.exports = __toCommonJS(index_exports);
// src/constants/index.ts
var AXIS = {
n: "\u25B2",
// Symbol for the top end of the Y-axis
ns: "\u2502",
// Vertical line for the Y-axis
y: "\u2524",
// Right tick mark on the Y-axis
nse: "\u2514",
// Bottom corner for the Y-axis meeting the X-axis
x: "\u252C",
// Top tick mark on the X-axis
we: "\u2500",
// Horizontal line for the X-axis
e: "\u25B6",
// Arrow symbol for the end of the X-axis
intersectionXY: "\u253C",
// Intersection of the X and Y axes
intersectionX: "\u2534",
// Bottom tick mark on the X-axis
intersectionY: "\u251C"
// Left tick mark on the Y-axis
};
var CHART = {
we: "\u2501",
// Bold horizontal line in the chart
wns: "\u2513",
// Top-right corner for vertical-to-horizontal connection
ns: "\u2503",
// Bold vertical line in the chart
nse: "\u2517",
// Bottom-left corner for vertical-to-horizontal connection
wsn: "\u251B",
// Bottom-right corner for vertical-to-horizontal connection
sne: "\u250F",
// Top-left corner for vertical-to-horizontal connection
area: "\u2588"
// Filled area symbol for chart representation
};
var EMPTY = " ";
var THRESHOLDS = {
x: "\u2501",
// Symbol for horizontal threshold line
y: "\u2503"
// Symbol for vertical threshold line
};
var POINT = "\u25CF";
var LAYOUT = {
MIN_PLOT_HEIGHT: 3,
DEFAULT_DECIMAL_PLACES: 3,
K_FORMAT_THRESHOLD: 1e3,
DEFAULT_PADDING: 2,
DEFAULT_Y_SHIFT_OFFSET: 1
};
// src/services/coords.ts
var normalize = (value) => value === void 0 ? [] : Array.isArray(value) ? value : [value];
var toEmpty = (size, empty = EMPTY) => Array(size >= 0 ? size : 0).fill(empty);
var toArray = (input) => {
return input.toString().split("");
};
var toUnique = (array) => [...new Set(array)];
var distance = (x, y) => Math.abs(Math.round(x) - Math.round(y));
var toFlat = (array) => [].concat(...array);
var toArrays = (array) => {
const rangeX = [];
const rangeY = [];
const flatArray = toFlat(array);
for (let i = 0; i < flatArray.length; i++) {
const [x, y] = flatArray[i];
rangeX.push(x);
rangeY.push(y);
}
return [toUnique(rangeX), toUnique(rangeY)];
};
var toSorted = (array) => array.sort(([x1], [x2]) => {
if (x1 < x2) return -1;
if (x1 > x2) return 1;
return 0;
});
var toPoint = (x, y) => [x ?? 0, y ?? 0];
var toPlot = (plotWidth, plotHeight) => (x, y) => [
Math.round(x / plotWidth * plotWidth),
plotHeight - 1 - Math.round(y / plotHeight * plotHeight)
];
var getExtrema = (arr, type = "max", position = 1) => arr.reduce(
(previous, curr) => Math[type](previous, curr[position]),
type === "max" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY
);
var getMax = (arr) => arr.reduce((previous, curr) => Math.max(previous, curr), Number.NEGATIVE_INFINITY);
var getMin = (arr) => arr.reduce((previous, curr) => Math.min(previous, curr), Number.POSITIVE_INFINITY);
var scaler = ([domainMin, domainMax], [rangeMin, rangeMax]) => {
const domainLength = Math.sqrt(Math.abs((domainMax - domainMin) ** 2)) || 1;
const rangeLength = Math.sqrt((rangeMax - rangeMin) ** 2);
return (domainValue) => rangeMin + rangeLength * (domainValue - domainMin) / domainLength;
};
var getPlotCoords = (coordinates, plotWidth, plotHeight, rangeX, rangeY) => {
const getXCoord = scaler(
rangeX || [getExtrema(coordinates, "min", 0), getExtrema(coordinates, "max", 0)],
[0, plotWidth - 1]
);
const getYCoord = scaler(rangeY || [getExtrema(coordinates, "min"), getExtrema(coordinates)], [
0,
plotHeight - 1
]);
return coordinates.map(([x, y]) => [getXCoord(x), getYCoord(y)]);
};
var getAxisCenter = (axisCenter, plotWidth, plotHeight, rangeX, rangeY, initialValue) => {
const axis = { x: initialValue[0], y: initialValue[1] };
if (axisCenter) {
const [x, y] = axisCenter;
if (typeof x === "number") {
const xScaler = scaler(rangeX, [0, plotWidth - 1]);
axis.x = Math.round(xScaler(x));
}
if (typeof y === "number") {
const yScaler = scaler(rangeY, [0, plotHeight - 1]);
axis.y = plotHeight - Math.round(yScaler(y));
}
}
return axis;
};
// src/services/settings.ts
var colorMap = {
ansiBlack: "\x1B[30m",
ansiRed: "\x1B[31m",
ansiGreen: "\x1B[32m",
ansiYellow: "\x1B[33m",
ansiBlue: "\x1B[34m",
ansiMagenta: "\x1B[35m",
ansiCyan: "\x1B[36m",
ansiWhite: "\x1B[37m"
};
var getAnsiColor = (color) => colorMap[color] || colorMap.ansiWhite;
var getChartSymbols = (color, series, chartSymbols, input, fillArea) => {
const chart = { ...CHART, ...chartSymbols };
if (fillArea) {
Object.entries(chart).forEach(([key]) => {
chart[key] = chart.area;
});
}
if (color) {
let currentColor = "ansiWhite";
if (Array.isArray(color)) {
currentColor = color[series];
} else if (typeof color === "function") {
currentColor = color(series, input);
} else {
currentColor = color;
}
Object.entries(chart).forEach(([key, symbol]) => {
chart[key] = `${getAnsiColor(currentColor)}${symbol}\x1B[0m`;
});
}
return chart;
};
var defaultFormatter = (value) => {
if (Math.abs(value) >= LAYOUT.K_FORMAT_THRESHOLD) {
const rounded = value / LAYOUT.K_FORMAT_THRESHOLD;
return rounded % 1 === 0 ? `${rounded}k` : `${rounded.toFixed(LAYOUT.DEFAULT_DECIMAL_PLACES)}k`;
}
return Number(value.toFixed(LAYOUT.DEFAULT_DECIMAL_PLACES));
};
// src/services/draw.ts
var drawPosition = ({
graph,
scaledX,
scaledY,
symbol,
debugMode = false
}) => {
if (debugMode) {
if (scaledY >= graph.length || scaledY < 0) {
console.log(`Drawing at [${scaledX}, ${scaledY}]`, "Error: out of bounds Y", {
graph,
scaledX,
scaledY
});
return;
}
if (scaledX >= graph[scaledY].length || scaledX < 0) {
console.log(`Drawing at [${scaledX}, ${scaledY}]`, "Error: out of bounds X", {
graph,
scaledX,
scaledY
});
return;
}
}
try {
graph[scaledY][scaledX] = symbol;
} catch (error) {
}
};
var drawXAxisEnd = ({
hasPlaceToRender,
yPos,
graph,
yShift,
i,
scaledX,
hideXAxisTicks,
pointXShift,
debugMode,
axisCenter
}) => {
if (hideXAxisTicks) {
return;
}
const yShiftWhenOccupied = hasPlaceToRender ? -1 : 0;
const yShiftWhenHasAxisCenter = 0;
let graphY = yPos + yShiftWhenOccupied + yShiftWhenHasAxisCenter;
if (graphY < 0) graphY = 0;
else if (graphY >= graph.length) graphY = graph.length - 1;
let graphX = scaledX + yShift - i + (axisCenter ? 1 : 2);
if (graphX < 0) graphX = 0;
else if (graphX >= graph[graphY].length) graphX = graph[graphY].length - 1;
drawPosition({
debugMode,
graph,
scaledX: graphX,
scaledY: graphY,
symbol: pointXShift[pointXShift.length - 1 - i]
});
};
var drawXAxisTick = ({
graph,
xPosition,
hideXAxisTicks,
axisSymbols,
debugMode,
axis
}) => {
if (hideXAxisTicks) {
return;
}
if (graph[axis.y][xPosition] === axisSymbols?.ns || graph[axis.y][xPosition] === axisSymbols?.we) {
drawPosition({
debugMode,
graph,
scaledX: xPosition,
scaledY: axis.y,
symbol: axisSymbols?.x || AXIS.x
});
}
};
var drawYAxisEnd = ({
graph,
scaledY,
yShift,
axis,
pointY,
transformLabel,
axisSymbols,
expansionX,
expansionY,
plotHeight,
showTickLabel,
hideYAxisTicks,
debugMode
}) => {
if (showTickLabel) {
const yMax = Math.max(...expansionY);
const yMin = Math.min(...expansionY);
const numTicks = plotHeight;
const yStep = (yMax - yMin) / numTicks;
for (let i = 0; i <= numTicks; i++) {
const yValue = Math.round(yMax - i * yStep);
const scaledYPos = (yMax - yValue) / (yMax - yMin) * (plotHeight - 1);
const graphYPos = Math.floor(scaledYPos) + 1;
if (graphYPos >= 0 && graphYPos < graph.length) {
const pointYShift = toArray(
transformLabel(yValue, { axis: "y", xRange: expansionX, yRange: expansionY })
);
for (let j = 0; j < pointYShift.length; j++) {
const colIndex = axis.x + yShift - j;
if (colIndex >= 0 && colIndex < graph[graphYPos].length) {
drawPosition({
debugMode,
graph,
scaledX: colIndex,
scaledY: graphYPos,
symbol: pointYShift[pointYShift.length - 1 - j]
});
}
}
const tickMarkIndex = axis.x + yShift + 1;
if (tickMarkIndex >= 0 && tickMarkIndex < graph[graphYPos].length) {
if (graph[graphYPos][tickMarkIndex] === axisSymbols?.ns || graph[graphYPos][tickMarkIndex] === axisSymbols?.we) {
drawPosition({
debugMode,
graph,
scaledX: tickMarkIndex,
scaledY: graphYPos,
symbol: axisSymbols?.y || AXIS.y
});
}
}
}
}
return;
}
if (hideYAxisTicks) {
return;
}
const row = scaledY + 1;
const col = axis.x + yShift + 1;
if (row >= 0 && row < graph.length && col >= 0 && graph[row] && col < graph[row].length && graph[row][col] !== axisSymbols?.y) {
const pointYShift = toArray(
transformLabel(pointY, { axis: "y", xRange: expansionX, yRange: expansionY })
);
for (let i = 0; i < pointYShift.length; i++) {
drawPosition({
debugMode,
graph,
scaledX: axis.x + yShift - i,
scaledY: scaledY + 1,
symbol: pointYShift[pointYShift.length - 1 - i]
});
}
if (graph[scaledY + 1][axis.x + yShift + 1] === axisSymbols?.ns || graph[scaledY + 1][axis.x + yShift + 1] === axisSymbols?.we) {
drawPosition({
debugMode,
graph,
scaledX: axis.x + yShift + 1,
scaledY: scaledY + 1,
symbol: axisSymbols?.y || AXIS.y
});
}
}
};
var drawAxis = ({
graph,
hideXAxis,
hideYAxis,
axisCenter,
axisSymbols,
axis,
debugMode
}) => {
for (let index = 0; index < graph.length; index++) {
const line = graph[index];
for (let curr = 0; curr < line.length; curr++) {
let lineChar = "";
if (curr === axis.x && !hideYAxis) {
if (index === 0) {
lineChar = axisSymbols?.n || AXIS.n;
} else if (index === graph.length - 1 && !axisCenter && !(hideYAxis || hideXAxis)) {
lineChar = axisSymbols?.nse || AXIS.nse;
} else {
lineChar = axisSymbols?.ns || AXIS.ns;
}
} else if (index === axis.y && !hideXAxis) {
if (curr === line.length - 1) {
lineChar = axisSymbols?.e || AXIS.e;
} else {
lineChar = axisSymbols?.we || AXIS.we;
}
}
if (lineChar) {
drawPosition({ debugMode, graph, scaledX: curr, scaledY: index, symbol: lineChar });
}
}
}
};
var drawGraph = ({
plotWidth,
plotHeight,
emptySymbol
}) => {
const callback = () => toEmpty(plotWidth + 2, emptySymbol);
return Array.from({ length: plotHeight + 2 }, callback);
};
var drawChart = ({ graph }) => `
${graph.map((line) => line.join("")).join("\n")}
`;
var drawCustomLine = ({
sortedCoords,
scaledX,
scaledY,
input,
index,
lineFormatter,
graph,
toPlotCoordinates,
expansionX,
expansionY,
minY,
minX,
debugMode
}) => {
const lineFormatterArgs = {
x: sortedCoords[index][0],
y: sortedCoords[index][1],
plotX: scaledX + 1,
plotY: scaledY + 1,
index,
input: input[0],
minY,
minX,
toPlotCoordinates,
expansionX,
expansionY
};
const customSymbols = lineFormatter(lineFormatterArgs);
if (Array.isArray(customSymbols)) {
customSymbols.forEach(({ x: symbolX, y: symbolY, symbol }) => {
drawPosition({ debugMode, graph, scaledX: symbolX, scaledY: symbolY, symbol });
});
} else {
drawPosition({
debugMode,
graph,
scaledX: customSymbols.x,
scaledY: customSymbols.y,
symbol: customSymbols.symbol
});
}
};
var drawLine = ({
index,
arr,
graph,
scaledX,
scaledY,
plotHeight,
emptySymbol,
chartSymbols,
axisCenter,
debugMode,
axis,
mode
}) => {
const [currX, currY] = arr[index];
if (mode === "bar" || mode === "horizontalBar") {
const positions = [];
const axisCenterShift = axisCenter ? 0 : 1;
if (mode === "bar") {
let i;
if (scaledY >= axis.y) {
i = scaledY;
while (i >= axis.y) {
positions.push([i, scaledX + axisCenterShift]);
i -= 1;
}
} else {
i = scaledY;
while (i <= axis.y) {
positions.push([i, scaledX + axisCenterShift]);
i += 1;
}
}
}
if (mode === "horizontalBar") {
let i;
if (scaledX >= axis.x) {
i = scaledX;
while (i >= axis.x) {
positions.push([scaledY + 1, i]);
i -= 1;
}
} else {
i = scaledX;
while (i <= axis.x) {
positions.push([scaledY + 1, i]);
i += 1;
}
}
}
positions.forEach(([y, x]) => {
drawPosition({
debugMode,
graph,
scaledX: x,
scaledY: y,
symbol: chartSymbols?.area || CHART.area
});
});
return;
}
if (mode === "point") {
drawPosition({
debugMode,
graph,
scaledX: scaledX + 1,
scaledY: scaledY + 1,
symbol: POINT
});
return;
}
if (index - 1 >= 0) {
const [prevX, prevY] = arr[index - 1];
Array(distance(currY, prevY)).fill("").forEach((_, steps, array) => {
if (Math.round(prevY) > Math.round(currY)) {
drawPosition({
debugMode,
graph,
scaledX,
scaledY: scaledY + 1,
symbol: chartSymbols?.nse || CHART.nse
});
if (steps === array.length - 1) {
drawPosition({
debugMode,
graph,
scaledX,
scaledY: scaledY - steps,
symbol: chartSymbols?.wns || CHART.wns
});
} else {
drawPosition({
debugMode,
graph,
scaledX,
scaledY: scaledY - steps,
symbol: chartSymbols?.ns || CHART.ns
});
}
} else {
drawPosition({
debugMode,
graph,
scaledX,
scaledY: scaledY + steps + 2,
symbol: chartSymbols?.wsn || CHART.wsn
});
drawPosition({
debugMode,
graph,
scaledX,
scaledY: scaledY + steps + 1,
symbol: chartSymbols?.ns || CHART.ns
});
}
});
if (Math.round(prevY) < Math.round(currY)) {
drawPosition({
debugMode,
graph,
scaledX,
scaledY: scaledY + 1,
symbol: chartSymbols?.sne || CHART.sne
});
} else if (Math.round(prevY) === Math.round(currY)) {
if (graph[scaledY + 1][scaledX] === emptySymbol) {
drawPosition({
debugMode,
graph,
scaledX,
scaledY: scaledY + 1,
symbol: chartSymbols?.we || CHART.we
});
}
}
const distanceX = distance(currX, prevX);
Array(distanceX ? distanceX - 1 : 0).fill("").forEach((_, steps) => {
const thisY = plotHeight - Math.round(prevY);
drawPosition({
debugMode,
graph,
scaledX: Math.round(prevX) + steps + 1,
scaledY: thisY,
symbol: chartSymbols?.we || CHART.we
});
});
}
if (arr.length - 1 === index) {
drawPosition({
debugMode,
graph,
scaledX: scaledX + 1,
scaledY: scaledY + 1,
symbol: chartSymbols?.we || CHART.we
});
}
};
var drawShift = ({
graph,
plotWidth,
emptySymbol,
scaledCoords,
xShift,
yShift
}) => {
let realYShift = 0;
graph.push(toEmpty(plotWidth + 2, emptySymbol));
realYShift += 1;
let step = plotWidth;
scaledCoords.forEach(([x], index) => {
if (scaledCoords[index - 1]) {
const current = x - scaledCoords[index - 1][0];
step = current <= step ? current : step;
}
});
const hasToBeMoved = step < xShift;
if (hasToBeMoved) {
realYShift += 1;
graph.push(toEmpty(plotWidth + 1, emptySymbol));
}
const realXShift = yShift + 1;
graph.forEach((line) => {
for (let i = 0; i <= yShift; i++) {
line.unshift(emptySymbol);
}
});
return { hasToBeMoved, realYShift, realXShift };
};
var getDrawYAxisTicks = ({
debugMode,
showTickLabel,
hideYAxisTicks,
plotHeight,
graph,
plotWidth,
yShift,
axis,
transformLabel,
axisSymbols,
expansionX,
expansionY
}) => (points) => {
const coords = getPlotCoords(points, plotWidth, plotHeight, expansionX, expansionY);
points.forEach(([_, pointY], i) => {
const [x, y] = coords[i];
const [, scaledY] = toPlot(plotWidth, plotHeight)(x, y);
drawYAxisEnd({
debugMode,
showTickLabel,
hideYAxisTicks,
plotHeight,
graph,
scaledY,
yShift,
axis,
pointY,
transformLabel,
axisSymbols,
expansionX,
expansionY
});
});
};
var getDrawXAxisTicks = ({
debugMode,
hideXAxisTicks,
plotWidth,
plotHeight,
yShift,
graph,
axisCenter,
emptySymbol,
axisSymbols,
axis,
transformLabel,
expansionX,
expansionY
}) => (points) => {
const coords = getPlotCoords(points, plotWidth, plotHeight, expansionX, expansionY);
points.forEach(([pointX], i) => {
const [x, y] = coords[i];
const [scaledX] = toPlot(plotWidth, plotHeight)(x, y);
const pointXShift = toArray(
transformLabel(pointX, { axis: "x", xRange: expansionX, yRange: expansionY })
);
const yPos = axisCenter ? axis.y + 1 : graph.length - 1;
const tickXPosition = scaledX + yShift + (axisCenter ? 1 : 2);
const hasPlaceToRender = pointXShift.every(
(_, j) => [emptySymbol, axisSymbols.ns].includes(graph[yPos - 1][scaledX + yShift - j + 2])
);
for (let j = 0; j < pointXShift.length; j++) {
const isOccupied = graph[yPos - 2][tickXPosition] === axisSymbols.x;
if (isOccupied) break;
drawXAxisEnd({
debugMode,
hasPlaceToRender,
yPos,
graph,
axisCenter,
yShift,
i: j,
scaledX,
hideXAxisTicks,
pointXShift
});
}
drawXAxisTick({
debugMode,
axis,
xPosition: tickXPosition,
graph,
hideXAxisTicks,
axisSymbols
});
});
};
var drawTicks = ({
input,
graph,
plotWidth,
plotHeight,
axis,
axisCenter,
yShift,
emptySymbol,
debugMode,
hideXAxis,
expansionX,
expansionY,
hideYAxis,
customYAxisTicks,
customXAxisTicks,
hideYAxisTicks,
hideXAxisTicks,
showTickLabel,
axisSymbols,
transformLabel
}) => {
const [minY, maxY] = [Math.min(...expansionY), Math.max(...expansionY)];
const [minX, maxX] = [Math.min(...expansionX), Math.max(...expansionX)];
const drawYAxisTicks = getDrawYAxisTicks({
axis,
axisSymbols,
debugMode,
expansionX,
expansionY,
graph,
hideYAxisTicks,
plotHeight,
plotWidth,
showTickLabel,
transformLabel,
yShift
});
const drawXAxisTicks = getDrawXAxisTicks({
axis,
axisCenter,
axisSymbols,
debugMode,
emptySymbol,
expansionX,
expansionY,
graph,
hideXAxisTicks,
plotHeight,
plotWidth,
transformLabel,
yShift
});
input.forEach((line) => {
line.forEach(([pointX, pointY]) => {
if (!hideYAxis && !customYAxisTicks) {
drawYAxisTicks([[pointX, pointY]]);
}
if (!hideXAxis && !customXAxisTicks) {
drawXAxisTicks([[pointX, pointY]]);
}
});
});
const filteredCustomYAxisTicks = (customYAxisTicks || []).filter((y) => y >= minY && y <= maxY);
const filteredCustomXAxisTicks = (customXAxisTicks || []).filter((x) => x >= minX && x <= maxX);
if (filteredCustomYAxisTicks.length && !hideYAxis) {
drawYAxisTicks(filteredCustomYAxisTicks.map((y) => toPoint(void 0, y)));
}
if (filteredCustomXAxisTicks.length && !hideXAxis) {
drawXAxisTicks(filteredCustomXAxisTicks.map((x) => toPoint(x)));
}
};
var drawAxisCenter = ({
graph,
realXShift,
axis,
debugMode,
axisSymbols,
emptySymbol,
backgroundSymbol
}) => {
const positionX = axis.x + realXShift;
const positionY = axis.y;
graph.forEach((line, indexY) => {
line.forEach((_, indexX) => {
if (indexX === positionX && indexY === positionY) {
let symbol = axisSymbols.nse;
const get = (x, y) => graph[y]?.[x];
const isEmpty = (value) => !Object.values(axisSymbols).includes(value) || value === backgroundSymbol || value === emptySymbol;
const emptyOnLeft = isEmpty(get(indexX - 1, indexY));
const emptyOnBottom = isEmpty(get(indexX, indexY + 1));
const emptyOnRight = isEmpty(get(indexX + 1, indexY));
const emptyOnTop = isEmpty(get(indexX, indexY - 1));
if (emptyOnLeft && emptyOnRight && !emptyOnBottom && !emptyOnTop) {
symbol = axisSymbols.ns;
} else if (emptyOnLeft && !emptyOnTop && !emptyOnBottom && !emptyOnRight) {
symbol = axisSymbols.intersectionY;
} else if (!emptyOnBottom && !emptyOnLeft && !emptyOnTop && !emptyOnRight) {
symbol = axisSymbols.intersectionXY;
} else if (emptyOnLeft && emptyOnTop && !emptyOnRight && emptyOnBottom) {
symbol = axisSymbols.we;
} else if (emptyOnLeft && emptyOnBottom && emptyOnRight && !emptyOnTop) {
symbol = axisSymbols.ns;
}
if (emptyOnBottom && emptyOnLeft && emptyOnRight && emptyOnTop) {
return;
}
drawPosition({
graph,
scaledX: indexX,
scaledY: indexY,
symbol,
debugMode
});
}
});
});
};
// src/services/overrides.ts
var setTitle = ({
title,
graph,
backgroundSymbol,
plotWidth,
yShift,
debugMode
}) => {
graph.unshift(toEmpty(plotWidth + yShift + 2, backgroundSymbol));
for (let index = 0; index < title.length; index++) {
const letter = title[index];
drawPosition({
debugMode,
graph,
scaledX: index,
scaledY: 0,
symbol: letter
});
}
};
var addXLabel = ({
graph,
plotWidth,
yShift,
backgroundSymbol,
xLabel,
debugMode
}) => {
const totalWidth = graph[0].length;
const labelLength = toArray(xLabel).length;
const startingPosition = Math.round((totalWidth - labelLength) / 2);
graph.push(toEmpty(plotWidth + yShift + 2, backgroundSymbol));
for (let index = 0; index < xLabel.length; index++) {
const letter = xLabel[index];
drawPosition({
debugMode,
graph,
scaledX: startingPosition + index,
scaledY: graph.length - 1,
symbol: letter
});
}
};
var addYLabel = ({
graph,
backgroundSymbol,
yLabel,
debugMode
}) => {
const totalHeight = graph.length;
const labelLength = toArray(yLabel).length;
const startingPosition = Math.round((totalHeight - labelLength) / 2) - 1;
const label = Array.from(yLabel);
graph.forEach((line, position) => {
line.unshift(backgroundSymbol);
if (position > startingPosition && label[position - startingPosition - 1]) {
drawPosition({
debugMode,
graph,
scaledX: 0,
scaledY: position,
symbol: label[position - startingPosition - 1]
});
}
});
};
var addLegend = ({
graph,
legend,
backgroundSymbol,
color,
symbols,
fillArea,
input,
pointSymbol,
debugMode,
points,
thresholds
}) => {
const legendSeries = legend.series ? normalize(legend.series) : [];
const legendPoints = legend.points ? normalize(legend.points) : [];
const legendThresholds = legend.thresholds ? normalize(legend.thresholds) : [];
const allLabels = [
...legendSeries.map((label, i) => ({
type: "series",
label,
index: i
})),
...legendThresholds.length > 0 ? [{ type: "spacer" }] : [],
...legendThresholds.map((label, i) => ({
type: "threshold",
label,
index: i
})),
...legendPoints.length > 0 ? [{ type: "spacer" }] : [],
...legendPoints.map((label, i) => ({
type: "point",
label,
index: i
}))
];
const legendWidth = 2 + Math.max(
...allLabels.map((entry) => {
if (entry.type === "spacer") return 0;
return toArray(entry.label).length;
})
);
const makePointSymbol = (index) => points && points[index]?.color ? `${getAnsiColor(points[index].color)}${pointSymbol}\x1B[0m` : pointSymbol;
const makeThresholdSymbol = (index) => thresholds && thresholds[index]?.color ? `${getAnsiColor(thresholds[index].color)}\u2503\x1B[0m` : "\u2503";
const makeLabelRow = (entry) => {
if (entry.type === "spacer") return Array(legendWidth).fill(backgroundSymbol);
const { label } = entry;
const labelText = toArray(label);
const pad = legendWidth - labelText.length - 2;
const padArr = Array(pad).fill(backgroundSymbol);
let symbol;
if (entry.type === "point") symbol = makePointSymbol(entry.index);
else if (entry.type === "threshold") symbol = makeThresholdSymbol(entry.index);
else symbol = getChartSymbols(color, entry.index, symbols?.chart, input, fillArea).area;
return [symbol, backgroundSymbol, ...labelText, ...padArr];
};
if (legend.position === "left") {
const newRows = allLabels.map(makeLabelRow);
for (let i = 0; i < graph.length; i++) {
const label = newRows[i];
if (label) {
for (let j = legendWidth - 1; j >= 0; j--) {
graph[i].unshift(label[j]);
}
} else {
for (let j = 0; j < legendWidth; j++) {
graph[i].unshift(backgroundSymbol);
}
}
}
}
if (legend.position === "right") {
for (let row of graph) {
for (let i = 0; i < legendWidth + 2; i++) {
row.push(backgroundSymbol);
}
}
const newRows = allLabels.map(makeLabelRow);
for (let i = 0; i < graph.length; i++) {
const label = newRows[i];
if (label) {
for (let j = 0; j < legendWidth; j++) {
const columnIndex = graph[i].length - legendWidth + j;
graph[i][columnIndex] = label[j];
}
}
}
}
if (legend.position === "top") {
allLabels;
const reversedEntries = allLabels.slice().reverse();
for (let entryIndex = 0; entryIndex < reversedEntries.length; entryIndex++) {
const entry = reversedEntries[entryIndex];
const line = makeLabelRow(entry);
const row = toEmpty(graph[0].length, backgroundSymbol);
graph.unshift(row);
for (let i = 0; i < line.length; i++) {
const symbol = line[i];
drawPosition({
debugMode,
graph,
scaledX: i,
scaledY: 0,
symbol
});
}
}
}
if (legend.position === "bottom" || !legend.position) {
for (let entryIndex = 0; entryIndex < allLabels.length; entryIndex++) {
const entry = allLabels[entryIndex];
const line = makeLabelRow(entry);
graph.push(toEmpty(graph[0].length, backgroundSymbol));
const y = graph.length - 1;
for (let i = 0; i < line.length; i++) {
const symbol = line[i];
drawPosition({
debugMode,
graph,
scaledX: i,
scaledY: y,
symbol
});
}
}
}
};
var addBorder = ({
graph,
borderSymbol,
backgroundSymbol
}) => {
const maxLength = Math.max(...graph.map((line) => line.length));
graph.forEach((line) => {
while (line.length < maxLength) line.push(backgroundSymbol);
line.unshift(borderSymbol);
line.push(borderSymbol);
});
graph.unshift(toEmpty(graph[0].length, borderSymbol));
graph.push(toEmpty(graph[0].length, borderSymbol));
};
var addBackgroundSymbol = ({
graph,
backgroundSymbol,
emptySymbol,
debugMode
}) => {
graph.forEach((line, curr) => {
for (let index = 0; index < line.length; index += 1) {
if (line[index] === emptySymbol) {
drawPosition({
debugMode,
graph,
scaledX: index,
scaledY: curr,
symbol: backgroundSymbol
});
} else break;
}
});
};
var addPoints = ({
graph,
points,
plotWidth,
plotHeight,
expansionX,
expansionY,
pointSymbol,
debugMode
}) => {
const mappedPoints = points.map(({ x, y }) => [x, y]);
const plotCoords = getPlotCoords(mappedPoints, plotWidth, plotHeight, expansionX, expansionY);
for (let pointNumber = 0; pointNumber < plotCoords.length; pointNumber++) {
const [x, y] = plotCoords[pointNumber];
const [scaledX, scaledY] = toPlot(plotWidth, plotHeight)(x, y);
drawPosition({
debugMode,
graph,
scaledX: scaledX + 1,
scaledY: scaledY + 1,
symbol: points[pointNumber]?.color ? `${getAnsiColor(points[pointNumber]?.color || "ansiRed")}${pointSymbol}\x1B[0m` : pointSymbol
});
}
};
var addThresholds = ({
graph,
thresholds,
axis,
plotWidth,
plotHeight,
expansionX,
expansionY,
thresholdSymbols,
debugMode
}) => {
const mappedThreshold = thresholds.map(({ x: thresholdX, y: thresholdY }) => {
let { x, y } = axis;
if (thresholdX) {
x = thresholdX;
}
if (thresholdY) {
y = thresholdY;
}
return [x, y];
});
const plotCoords = getPlotCoords(mappedThreshold, plotWidth, plotHeight, expansionX, expansionY);
for (let thresholdNumber = 0; thresholdNumber < plotCoords.length; thresholdNumber++) {
const [x, y] = plotCoords[thresholdNumber];
const [scaledX, scaledY] = toPlot(plotWidth, plotHeight)(x, y);
if (thresholds[thresholdNumber]?.x && graph[0][scaledX]) {
for (let index = 0; index < graph.length; index++) {
if (graph[index][scaledX]) {
drawPosition({
debugMode,
graph,
scaledX: scaledX + 1,
scaledY: index,
symbol: thresholds[thresholdNumber]?.color ? `${getAnsiColor(thresholds[thresholdNumber]?.color || "ansiRed")}${thresholdSymbols.y}\x1B[0m` : thresholdSymbols.y
});
}
}
}
if (thresholds[thresholdNumber]?.y && graph[scaledY]) {
for (let index = 0; index < graph[scaledY].length; index++) {
if (graph[scaledY][index]) {
drawPosition({
debugMode,
graph,
scaledX: index,
scaledY: scaledY + 1,
symbol: thresholds[thresholdNumber]?.color ? `${getAnsiColor(thresholds[thresholdNumber]?.color || "ansiRed")}${thresholdSymbols.x}\x1B[0m` : thresholdSymbols.x
});
}
}
}
}
};
var setFillArea = ({
graph,
chartSymbols,
debugMode
}) => {
for (let yIndex = 0; yIndex < graph.length; yIndex++) {
const xValues = graph[yIndex];
for (let xIndex = 0; xIndex < xValues.length; xIndex++) {
const xSymbol = xValues[xIndex];
let areaSymbol = chartSymbols?.area || CHART.area;
if (xSymbol === chartSymbols?.nse || xSymbol === chartSymbols?.wsn || xSymbol === chartSymbols?.we || xSymbol === areaSymbol) {
if (graph[yIndex + 1]?.[xIndex]) {
drawPosition({
debugMode,
graph,
scaledX: xIndex,
scaledY: yIndex + 1,
symbol: areaSymbol
});
}
}
}
}
};
var removeEmptyLines = ({
graph,
backgroundSymbol
}) => {
const elementsToRemove = [];
graph.forEach((line, position) => {
if (line.every((symbol) => symbol === backgroundSymbol)) {
elementsToRemove.push(position);
}
if (graph.every((currentLine) => currentLine[0] === backgroundSymbol)) {
graph.forEach((currentLine) => currentLine.shift());
}
});
elementsToRemove.reverse().forEach((position) => {
graph.splice(position, 1);
});
};
var getTransformLabel = ({ formatter }) => (value, helpers) => formatter ? formatter(value, helpers) : defaultFormatter(value, helpers);
// src/services/defaults.ts
var getSymbols = ({ symbols }) => {
const emptySymbol = symbols?.empty || EMPTY;
return {
axisSymbols: { ...AXIS, ...symbols?.axis },
emptySymbol,
backgroundSymbol: symbols?.background || emptySymbol,
borderSymbol: symbols?.border,
thresholdSymbols: {
x: symbols?.thresholds?.x || THRESHOLDS.x,
y: symbols?.thresholds?.y || THRESHOLDS.y
},
pointSymbol: symbols?.point || POINT
};
};
var getChartSize = ({
input,
width,
height,
yRange,
axisCenter
}) => {
const [inputRangeX, inputRangeY] = toArrays(input);
const rangeX = [...inputRangeX, axisCenter?.[0]].filter((v) => typeof v === "number");
const rangeY = [...inputRangeY, axisCenter?.[1]].filter((v) => typeof v === "number");
const minX = getMin(rangeX);
const maxX = getMax(rangeX);
const minY = getMin(rangeY);
const maxY = getMax(rangeY);
const expansionX = [minX, maxX];
const expansionY = yRange || [minY, maxY];
const plotWidth = width || rangeX.length;
let plotHeight = Math.round(height || maxY - minY + 1);
if (!height && plotHeight < LAYOUT.MIN_PLOT_HEIGHT) {
plotHeight = rangeY.length;
}
return {
minX,
minY,
plotWidth,
plotHeight,
expansionX,
expansionY
};
};
var getLabelShift = ({
input,
transformLabel,
expansionX,
expansionY,
minX,
showTickLabel
}) => {
const getLength = (value, axis) => {
const formatted = transformLabel(value, { axis, xRange: expansionX, yRange: expansionY });
return toArray(formatted).length;
};
const points = input.flat();
const { x: xShift, y: longestY } = points.reduce(
(acc, [x, y]) => ({
x: Math.max(acc.x, getLength(x, "x")),
y: Math.max(acc.y, getLength(y, "y"))
}),
{ x: 0, y: 0 }
);
if (!showTickLabel) {
const minXLength = getLength(minX, "x");
const baseShift = Math.max(0, minXLength - LAYOUT.DEFAULT_PADDING);
return {
xShift,
yShift: Math.max(baseShift, longestY)
};
}
return { xShift, yShift: longestY + LAYOUT.DEFAULT_Y_SHIFT_OFFSET };
};
var getInput = ({ rawInput }) => {
let input = rawInput;
if (typeof input[0]?.[0] === "number") {
input = [rawInput];
}
return input;
};
// src/services/plot.ts
var plot = (rawInput, {
color,
width,
height,
yRange,
showTickLabel,
hideXAxisTicks,
hideYAxisTicks,
customXAxisTicks,
customYAxisTicks,
axisCenter,
formatter,
lineFormatter,
symbols,
title,
fillArea,
hideXAxis,
hideYAxis,
xLabel,
yLabel,
legend,
thresholds,
points,
debugMode,
mode = "line"
} = {}) => {
let input = getInput({ rawInput });
if (yRange) {
const [yMin, yMax] = yRange;
input = input.map((line) => line.filter(([, y]) => y >= yMin && y <= yMax));
}
if (input.length === 0) {
return "";
}
const transformLabel = getTransformLabel({ formatter });
let scaledCoords = [[0, 0]];
const { minX, minY, plotWidth, plotHeight, expansionX, expansionY } = getChartSize({
width,
height,
input,
yRange,
axisCenter
});
const {
axisSymbols,
emptySymbol,
backgroundSymbol,
borderSymbol,
thresholdSymbols,
pointSymbol
} = getSymbols({ symbols });
const graph = drawGraph({ plotWidth, plotHeight, emptySymbol });
const axis = getAxisCenter(axisCenter, plotWidth, plotHeight, expansionX, expansionY, [
0,
graph.length - 1
]);
for (let series = 0; series < input.length; series++) {
const coords = input[series];
const chartSymbols = getChartSymbols(color, series, symbols?.chart, input, fillArea);
const sortedCoords = toSorted(coords);
scaledCoords = getPlotCoords(sortedCoords, plotWidth, plotHeight, expansionX, expansionY).map(
([x, y], index, arr) => {
const toPlotCoordinates = toPlot(plotWidth, plotHeight);
const [scaledX, scaledY] = toPlotCoordinates(x, y);
if (!lineFormatter) {
drawLine({
mode,
debugMode,
index,
arr,
graph,
scaledX,
scaledY,
plotHeight,
emptySymbol,
chartSymbols,
axis,
axisCenter
});
if (fillArea) {
setFillArea({ graph, chartSymbols, debugMode });
}
} else {
drawCustomLine({
debugMode,
sortedCoords,
scaledX,
scaledY,
input,
index,
lineFormatter,
graph,
toPlotCoordinates,
expansionX,
expansionY,
minY,
minX
});
}
return [scaledX, scaledY];
}
);
}
if (thresholds) {
addThresholds({
thresholdSymbols,
debugMode,
graph,
thresholds,
axis,
plotWidth,
plotHeight,
expansionX,
expansionY
});
}
if (points) {
addPoints({
pointSymbol,
debugMode,
graph,
points,
plotWidth,
plotHeight,
expansionX,
expansionY
});
}
drawAxis({
debugMode,
graph,
hideXAxis,
hideYAxis,
axisCenter,
axisSymbols,
axis
});
const { xShift, yShift } = getLabelShift({
input,
transformLabel,
showTickLabel,
expansionX,
expansionY,
minX
});
let { realXShift } = drawShift({
graph,
plotWidth,
emptySymbol,
scaledCoords,
xShift,
yShift
});
if (backgroundSymbol) {
addBackgroundSymbol({ debugMode, graph, backgroundSymbol, emptySymbol });
}
drawAxisCenter({
realXShift,
debugMode,
emptySymbol,
backgroundSymbol,
graph,
axisSymbols,
axis
});
drawTicks({
input,
graph,
plotWidth,
plotHeight,
axis,
axisCenter,
yShift,
emptySymbol,
debugMode,
hideXAxis,
expansionX,
expansionY,
hideYAxis,
customYAxisTicks,
customXAxisTicks,
hideYAxisTicks,
hideXAxisTicks,
showTickLabel,
axisSymbols,
transformLabel
});
removeEmptyLines({ graph, backgroundSymbol });
if (xLabel) {
addXLabel({
debugMode,
xLabel,
graph,
backgroundSymbol,
plotWidth,
yShift
});
}
if (yLabel) {
addYLabel({
debugMode,
yLabel,
graph,
backgroundSymbol
});
}
if (legend) {
addLegend({
points,
thresholds,
pointSymbol,
debugMode,
input,
graph,
legend,
backgroundSymbol,
color,
symbols,
fillArea
});
}
if (title) {
setTitle({
debugMode,
title,
graph,
backgroundSymbol,
plotWidth,
yShift
});
}
if (borderSymbol) {
addBorder({ graph, borderSymbol, backgroundSymbol });
}
return drawChart({ graph });
};
// src/index.ts
var index_default = plot;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AXIS,
CHART,
EMPTY,
LAYOUT,
POINT,
THRESHOLDS,
plot
});
//# sourceMappingURL=index.cjs.map