Skip to content

Commit

Permalink
chore: add support for range and coloring
Browse files Browse the repository at this point in the history
  • Loading branch information
gtktsc committed Oct 24, 2024
1 parent 58eee2f commit 7505686
Show file tree
Hide file tree
Showing 12 changed files with 407 additions and 194 deletions.
18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "simple-ascii-chart",
"version": "4.0.6",
"version": "4.1.0",
"description": "Simple ascii chart generator",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand All @@ -19,22 +19,22 @@
"postversion": "git push && git push --tags"
},
"devDependencies": {
"@types/jest": "^29.5.5",
"@types/node": "^20.6.2",
"@types/jest": "^29.5.12",
"@types/node": "^20.14.9",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"codecov": "^3.8.3",
"eslint": "^8.49.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.28.1",
"husky": "^8.0.3",
"husky": "^9.0.11",
"jest": "^29.7.0",
"lint-staged": "^14.0.1",
"prettier": "^3.0.3",
"ts-jest": "^29.1.1",
"tsc-watch": "^6.0.4",
"typescript": "^5.2.2"
"lint-staged": "^15.2.7",
"prettier": "^3.3.2",
"ts-jest": "^29.1.5",
"tsc-watch": "^6.2.0",
"typescript": "^5.5.3"
},
"lint-staged": {
"*.ts": "eslint . --ext .ts,.js --fix",
Expand Down
7 changes: 7 additions & 0 deletions src/services/__tests__/draw.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe('Drawing functions', () => {
graph,
yShift: 1,
i: 0,
plotHeight: 3,
scaledX: 1,
shift: 0,
signShift: 0,
Expand All @@ -47,10 +48,13 @@ describe('Drawing functions', () => {
yShift: 0,
axis: { x: 0, y: 0 },
pointY: 1,
plotHeight: 3,
transformLabel: (value: number) => value.toString(),
axisSymbols: { y: 'Y' },
expansionX: [0],
expansionY: [0, 1, 2],
coordsGetter: () => [0, 0] as Point,
plotGetter: () => [0, 0] as Point,
};

drawYAxisEnd(params);
Expand All @@ -71,10 +75,13 @@ describe('Drawing functions', () => {
yShift: 1,
axis: { x: 1, y: 1 },
pointY: 2,
plotHeight: 2,
transformLabel: (value: number) => value.toString(),
axisSymbols: AXIS,
expansionX: [],
expansionY: [],
coordsGetter: () => [0, 0] as Point,
plotGetter: () => [0, 0] as Point,
};
drawYAxisEnd(args);
expect(graph[2][3]).toEqual(AXIS.y);
Expand Down
2 changes: 1 addition & 1 deletion src/services/__tests__/overrides.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('Graph Utility Functions', () => {
it('should add the legend correctly', () => {
const legend = { position: 'top', series: ['A', 'B'] } as Legend;

addLegend({ legend, graph, backgroundSymbol });
addLegend({ legend, graph, backgroundSymbol, input: [[]] });

expect(graph[0].join('')).toContain('A');
expect(graph[1].join('')).toContain('B');
Expand Down
2 changes: 1 addition & 1 deletion src/services/__tests__/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('getChartSymbols', () => {
[['ansiBlack', 'ansiRed'], '\u001b[31m', 1],
])('', (input, output, series) => {
it(input.toString(), () => {
const formatted = getChartSymbols(input as Color, series);
const formatted = getChartSymbols(input as Color, series, undefined, [[]], false);
expect(formatted.we).toBe(`${output}━\u001b[0m`);
expect(formatted.wns).toBe(`${output}┓\u001b[0m`);
expect(formatted.ns).toBe(`${output}┃\u001b[0m`);
Expand Down
52 changes: 38 additions & 14 deletions src/services/coords.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,26 @@ export const toSorted = (array: SingleLine): SingleLine =>
* @param {number} plotHeight - The height of the plot.
* @returns {function} - A function that takes (x, y) and returns plot coordinates [scaledX, scaledY].
*/
export const toPlot = (plotWidth: number, plotHeight: number) => (x: number, y: number) => [
Math.round((x / plotWidth) * plotWidth),
plotHeight - 1 - Math.round((y / plotHeight) * plotHeight),
];
export const toPlot =
(plotWidth: number, plotHeight: number) =>
(x: number, y: number): Point => [
Math.round((x / plotWidth) * plotWidth),
plotHeight - 1 - Math.round((y / plotHeight) * plotHeight),
];

/**
* Converts plot coordinates (scaledX, scaledY) back to the original coordinates in the specified plot dimensions.
* @param {number} plotWidth - The width of the plot.
* @param {number} plotHeight - The height of the plot.
* @returns {function} - A function that takes (scaledX, scaledY) and returns original coordinates [x, y].
*/
export const fromPlot =
(plotWidth: number, plotHeight: number) =>
(scaledX: number, scaledY: number): [number, number] => {
const x = (scaledX / plotWidth) * plotWidth;
const y = (plotHeight - 1 - scaledY) * (plotHeight / plotHeight);
return [Math.round(x), Math.round(y)];
};

/**
* Finds the maximum or minimum value in a single-line array of points.
Expand Down Expand Up @@ -179,10 +195,9 @@ export const getPlotCoords = (

return coordinates.map(toScale);
};

/**
* Gets the center point for an axis.
* @param {Point | void} axisCenter - The center point of the axis (optional).
* @param {Point | [number | undefined, number | undefined] | undefined} axisCenter - The center point of the axis.
* @param {number} plotWidth - The width of the plot.
* @param {number} plotHeight - The height of the plot.
* @param {number[]} rangeX - The range of x values.
Expand All @@ -191,20 +206,29 @@ export const getPlotCoords = (
* @returns {Point} - The center point of the axis.
*/
export const getAxisCenter = (
axisCenter: Point | void,
axisCenter: Point | [number | undefined, number | undefined] | undefined,
plotWidth: number,
plotHeight: number,
rangeX: number[],
rangeY: number[],
initialValue: number[],
) => {
initialValue: [number, number],
): { x: number; y: number } => {
const axis = { x: initialValue[0], y: initialValue[1] };
// calculate axis position

if (axisCenter) {
const [centerX, centerY] = toCoordinates(axisCenter, plotWidth, plotHeight, rangeX, rangeY);
const [plotCenterX, plotCenterY] = toPlot(plotWidth, plotHeight)(centerX, centerY);
axis.x = plotCenterX;
axis.y = plotCenterY + 1;
const [x, y] = axisCenter;

if (typeof x === 'number') {
const xScaler = scaler(rangeX, [0, plotWidth - 1]);
const xCoord = xScaler(x);
axis.x = Math.round(xCoord);
}

if (typeof y === 'number') {
const yScaler = scaler(rangeY, [0, plotHeight - 1]);
const yCoord = yScaler(y);
axis.y = plotHeight - Math.round(yCoord);
}
}

return axis;
Expand Down
4 changes: 3 additions & 1 deletion src/services/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ export const getChartSize = ({
input,
width,
height,
yRange,
}: {
input: MultiLine;
width?: number;
height?: number;
yRange?: [number, number];
}) => {
const [rangeX, rangeY] = toArrays(input);

Expand All @@ -32,7 +34,7 @@ export const getChartSize = ({
const maxY = getMax(rangeY);

const expansionX = [minX, maxX];
const expansionY = [minY, maxY];
const expansionY = yRange || [minY, maxY];

// set default size
const plotWidth = width || rangeX.length;
Expand Down
97 changes: 89 additions & 8 deletions src/services/draw.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AXIS, CHART } from '../constants';
import { CustomSymbol, Formatter, Graph, MultiLine, Point, Symbols } from '../types';
import { CustomSymbol, Formatter, Graph, MaybePoint, MultiLine, Point, Symbols } from '../types';
import { distance, toArray, toEmpty } from './coords';

export const drawXAxisEnd = ({
Expand All @@ -16,7 +16,7 @@ export const drawXAxisEnd = ({
pointXShift,
}: {
hasPlaceToRender: boolean;
axisCenter?: Point;
axisCenter?: Point | [number | undefined, number | undefined];
yPos: number;
graph: Graph;
yShift: number;
Expand All @@ -28,15 +28,37 @@ export const drawXAxisEnd = ({
pointXShift: string[];
}) => {
const yShiftWhenOccupied = hasPlaceToRender ? -1 : 0;
const yShiftWhenHasAxisCenter = axisCenter ? 1 : 0;
const yShiftWhenHasAxisCenter = axisCenter && axisCenter[1] !== undefined ? 1 : 0;

const graphY = yPos + yShiftWhenOccupied + yShiftWhenHasAxisCenter;
const graphX = scaledX + yShift - i + 2 + shift;
let graphY = yPos + yShiftWhenOccupied + yShiftWhenHasAxisCenter;

// Boundary check
if (graphY < 0) {
graphY = 0;
} else if (graphY >= graph.length) {
graphY = graph.length - 1;
}

let graphX = scaledX + yShift - i + 2 + shift;

// Ensure graphX is within bounds
if (graphX < 0) {
graphX = 0;
} else if (graphX >= graph[graphY].length) {
graphX = graph[graphY].length - 1;
}

graph[graphY][graphX] = pointXShift[pointXShift.length - 1 - i];

// Add X tick only for the last value
if (pointXShift.length - 1 === i) {
graph[yPos + signShift][scaledX + yShift + 2 + shift] = axisSymbols?.x || AXIS.x;
const xTickY = yPos + signShift;
const xTickX = scaledX + yShift + 2 + shift;

// Ensure xTickY and xTickX are within bounds
if (xTickY >= 0 && xTickY < graph.length && xTickX >= 0 && xTickX < graph[xTickY].length) {
graph[xTickY][xTickX] = axisSymbols?.x || AXIS.x;
}
}
};

Expand All @@ -45,23 +67,82 @@ export const drawYAxisEnd = ({
scaledY,
yShift,
axis,
axisCenter,
pointY,
transformLabel,
axisSymbols,
expansionX,
expansionY,
plotHeight,
showTickLabel,
}: {
graph: Graph;
scaledY: number;
yShift: number;
plotHeight: number;
axis: { x: number; y: number };
axisCenter?: MaybePoint;
pointY: number;
transformLabel: Formatter;
axisSymbols: Symbols['axis'];
expansionX: number[];
expansionY: number[];
showTickLabel?: boolean;
}) => {
// make sure position is not taken already
// Show all labels when showTickLabel is true
if (showTickLabel) {
const yMax = Math.max(...expansionY);
const yMin = Math.min(...expansionY);

// Decide the number of ticks you want on the Y-axis
const numTicks = plotHeight; // You can adjust this number as needed

// Calculate the step size for each tick
const yStep = (yMax - yMin) / numTicks;

for (let i = 0; i <= numTicks; i += 1) {
// Calculate the Y value for this tick
const yValue = yMax - i * yStep;

// Map the Y value to a graph Y position
const scaledYPos = ((yMax - yValue) / (yMax - yMin)) * (plotHeight - 1);

const labelShift = axisCenter?.[1] !== undefined && axisCenter?.[1] > 0 ? 1 : 0;

// Round to get the exact row index in the graph array
const graphYPos = Math.floor(scaledYPos) + 1 + labelShift;

// Ensure the graphYPos is within the bounds of the graph array
if (graphYPos >= 0 && graphYPos < graph.length) {
// Check if the position is not already occupied
if (graph[graphYPos][axis.x + yShift + 1] !== axisSymbols?.y) {
const pointYShift = toArray(
transformLabel(yValue, { axis: 'y', xRange: expansionX, yRange: expansionY }),
);

// Place the tick label on the graph
for (let j = 0; j < pointYShift.length; j += 1) {
const colIndex = axis.x + yShift - j;

// Ensure colIndex is within bounds
if (colIndex >= 0 && colIndex < graph[graphYPos].length) {
graph[graphYPos][colIndex] = pointYShift[pointYShift.length - 1 - j];
}
}

const tickMarkIndex = axis.x + yShift + 1;

// Ensure tickMarkIndex is within bounds
if (tickMarkIndex >= 0 && tickMarkIndex < graph[graphYPos].length) {
graph[graphYPos][tickMarkIndex] = axisSymbols?.y || AXIS.y;
}
}
}
}
return;
}

// Existing code for showing only values that are present
if (graph[scaledY + 1][axis.x + yShift + 1] !== axisSymbols?.y) {
const pointYShift = toArray(
transformLabel(pointY, { axis: 'y', xRange: expansionX, yRange: expansionY }),
Expand All @@ -84,7 +165,7 @@ export const drawAxis = ({
graph: Graph;
axis: { x: number; y: number };
hideXAxis?: boolean;
axisCenter?: Point;
axisCenter?: MaybePoint;
hideYAxis?: boolean;
axisSymbols: Symbols['axis'];
}) => {
Expand Down
Loading

0 comments on commit 7505686

Please sign in to comment.