UNPKG

simple-ascii-chart

Version:
1,497 lines (1,490 loc) 39 kB
// 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; export { AXIS, CHART, EMPTY, LAYOUT, POINT, THRESHOLDS, index_default as default, plot }; //# sourceMappingURL=index.js.map