Simple ASCII Chart is a TypeScript package that allows you to create ASCII charts in your terminal. It operates on two-dimensional input data, supports multiple series, custom colors, and formatters to make your data visualization clear and customizable.
With colored multiline:
With colored area:
With axis:
Package can be imported via yarn (or npm):
yarn add simple-ascii-chart
And used like:
import plot from 'simple-ascii-chart';
const graph = plot(input, settings);
Alternatively, you can create a graph interactively in the playground.
You can also use the API endpoint to generate charts by sending a POST request with your input data:
curl -d input='[[1,2],[2,3],[3,4]]' -G https://simple-ascii-chart.vercel.app/api
or as a URL parameter:
https://simple-ascii-chart.vercel.app/api?input=[[1,2],[2,3],[3,4]]&settings={%22width%22:50}
When dependency is imported to project:
import plot from 'simple-ascii-chart';
console.log(
plot(
[
[1, 1],
[2, 4],
[3, 4],
[4, 2],
[5, -1],
[6, 3],
[7, -1],
[8, 9],
],
{ title: 'Important data', width: 20, height: 8 },
),
);
And 🎉, chart appears in your terminal:
Important data
▲
9┤ ┏━
│ ┃
│ ┃
4┤ ┏━━━━┓ ┃
3┤ ┃ ┃ ┏━┓ ┃
2┤ ┃ ┗━━┓ ┃ ┃ ┃
1┤━━┛ ┃ ┃ ┃ ┃
-1┤ ┗━━┛ ┗━━┛
└┬──┬─┬──┬──┬──┬─┬──┬▶
1 2 3 4 5 6 7 8
Input has to be a two dimensional array or array of arrays for series:
Point = [x: number, y: number];
SingleLine = Point[];
MultiLine = SingleLine[];
Input = SingleLine | MultiLine;
Therefore input for a single series is:
const input = [
[1, 1],
[2, 4],
[3, 40],
];
Or for multi-series:
const input = [
[
[0, 18],
[1, 1],
[2, 3],
],
[
[4, 1],
[5, 0],
[6, 1],
],
];
Plot can be adjusted with a second parameter settings
.
Changes graph color. Possible values are:
color?:
| 'ansiRed'
| 'ansiGreen'
| 'ansiBlack'
| 'ansiYellow'
| 'ansiBlue'
| 'ansiMagenta'
| 'ansiCyan'
| 'ansiWhite'
Can be used to distinguish series like:
color: ['ansiGreen', 'ansiRed'];
Changes default width of the graph, can be used to scale up/down values:
width?: number
Changes default height of the graph, can be used to scale up/down values:
height?: number
Changes center of the axis, by default it's placed in the bottom-left:
axisCenter?: [x:number, y:number]
Transforms axis label:
formatter?: Formatter
Where
type FormatterHelpers = {
axis: 'x' | 'y';
xRange: number[];
yRange: number[];
};
type Formatter = (number: number, helpers: FormatterHelpers) => number | string;
Default formatter is:
defaultFormatter: Formatter = (value, { xRange, yRange }) => {
// Cut off small values
if (Math.abs(xRange[0]) < 1000 || Math.abs(yRange[0]) < 1000) {
return Number(value.toFixed(3));
}
// Adds XYZk to cut off large values
if (Math.abs(value) > 1000) return `${value / 1000}k`;
return value;
};
Transforms line, allows to format graph style. Callback takes arguments:
LineFormatterArgs = {
x: number;
y: number;
plotX: number;
plotY: number;
input: SingleLine;
index: number;
};
plotX
and plotY
is coordinate of a point scaled to the plot. Callback has to return:
CustomSymbol = { x: number; y: number; symbol: string };
where x
and y
is also plot coordinate, symbol
is char to be displayed. If an array is returned, more points can be placed on the graph.
lineFormatter?: (args: LineFormatterArgs) => CustomSymbol | CustomSymbol[];
Check examples section for real world usage.
Adds title above the graph:
title?: string;
Adds label to the x axis:
xLabe
l?: string;
Adds label to the y axis:
yLabel?: string;
Adds thresholds to plot:
thresholds?: {
x?: number;
y?: number;
color?: Color;
}[];
Some graphs look better presented as a area, not lines. In order to use area chart, pass fillArea prop:
fillArea?: boolean;
Hide X axis:
hideXAxis? boolean;
Hide Y axis:
hideYAxis? boolean;
Overrides default symbols. Three independent sections are: empty
- background, axis
- symbols used to draw axis, chart
- symbols used to draw graph.
symbols: {
background: ' ',
border: undefined,
empty: ' ',
axis: {
n: '▲',
ns: '│',
y: '┤',
nse: '└',
x: '┬',
we: '─',
e: '▶',
},
chart: {
we: '━',
wns: '┓',
ns: '┃',
nse: '┗',
wsn: '┛',
sne: '┏',
area: '█'
}
Settings = {
color?: Color | Color[];
width?: number;
height?: number;
axisCenter?: [number, number];
formatter?: (number:number) => number;
lineFormatter?: (args: LineFormatterArgs) => CustomSymbol | CustomSymbol[];
hideXAxis?: boolean;
legend?: { position?: 'left' | 'right' | 'top' | 'bottom'; series: string | string[] };
title?: string;
xLabel?: string;
yLabel?: string;
fillArea?: boolean;
hideYAxis?: boolean;
thresholds?: {
x?: number;
y?: number;
color?: Color;
}[];
symbols?: {
background?: ' ',
border?: undefined,
empty?: ' ',
axis?: {
n: '▲',
ns: '│',
y: '┤',
nse: '└',
x: '┬',
we: '─',
e: '▶',
},
chart?: {
we: '━',
wns: '┓',
ns: '┃',
nse: '┗',
wsn: '┛',
sne: '┏',
area: '█',
}
}
};
Input:
plot(
[
[1, 1],
[2, 4],
[3, 4],
[4, 2],
[5, -1],
],
{ width: 9, height: 6 },
);
Output:
▲
4┤ ┏━━━┓
│ ┃ ┃
2┤ ┃ ┗━┓
1┤━┛ ┃
│ ┃
-1┤ ┗━
└┬─┬─┬─┬─┬▶
1 2 3 4 5
Input:
plot(
[
[1, 1],
[2, 4],
[3, 4],
[4, 2],
[5, -1],
[6, 3],
[7, -1],
[8, 9],
],
{ title: 'Important data', width: 20, height: 8 },
);
Output:
Important data
▲
9┤ ┏━
│ ┃
│ ┃
4┤ ┏━━━━┓ ┃
3┤ ┃ ┃ ┏━┓ ┃
2┤ ┃ ┗━━┓ ┃ ┃ ┃
1┤━━┛ ┃ ┃ ┃ ┃
-1┤ ┗━━┛ ┗━━┛
└┬──┬─┬──┬──┬──┬─┬──┬▶
1 2 3 4 5 6 7 8
Input:
plot(
[
[1, 1],
[2, 4],
[3, 4],
[4, 2],
[5, -1],
[6, 3],
[7, -1],
[8, 9],
],
{ xLabel: 'x', yLabel: 'y', width: 20, height: 8 },
);
Output:
▲
9┤ ┏━
│ ┃
│ ┃
4┤ ┏━━━━┓ ┃
3┤ ┃ ┃ ┏━┓ ┃
y 2┤ ┃ ┗━━┓ ┃ ┃ ┃
1┤━━┛ ┃ ┃ ┃ ┃
-1┤ ┗━━┛ ┗━━┛
└┬──┬─┬──┬──┬──┬─┬──┬▶
1 2 3 4 5 6 7 8
x
Input:
plot(
[
[
[1, 1],
[2, 2],
[3, 4],
[4, 6],
],
[
[5, 4],
[6, 1],
[7, 2],
[8, 3],
],
],
{
width: 20,
fillArea: true,
color: ['ansiGreen', 'ansiBlue'],
legend: { position: 'bottom', series: ['first', 'second'] },
},
);
Output:
▲
6┤ ██
│ ██
4┤ █████ ███
3┤ █████ ███ ██
2┤ ███████ ███ █████
1┤█████████ █████████
└┬──┬─┬──┬──┬──┬─┬──┬▶
1 2 3 4 5 6 7 8
█ first
█ second
Input:
plot(
[
[1, 1],
[2, 4],
[3, 4],
[4, 2],
[5, -1],
[6, 3],
[7, -1],
[8, 9],
],
{ symbols: { border: '█' }, xLabel: 'x', yLabel: 'y', width: 20, height: 8 },
);
Output:
███████████████████████████
█ ▲ █
█ 9┤ ┏━ █
█ │ ┃ █
█ │ ┃ █
█ 4┤ ┏━━━━┓ ┃ █
█ 3┤ ┃ ┃ ┏━┓ ┃ █
█y 2┤ ┃ ┗━━┓ ┃ ┃ ┃ █
█ 1┤━━┛ ┃ ┃ ┃ ┃ █
█ -1┤ ┗━━┛ ┗━━┛ █
█ └┬──┬─┬──┬──┬──┬─┬──┬▶█
█ 1 2 3 4 5 6 7 8 █
█ x █
███████████████████████████
Input:
plot(
[
[
[1, 1],
[2, 2],
[3, 4],
[4, 6],
],
[
[1, 4],
[2, 1],
[3, 2],
[4, 3],
],
],
{
fillArea: true,
color: ['ansiGreen', 'ansiBlue'],
},
);
Output:
▲
6┤ ██
│ ██
4┤████
3┤████
2┤████
1┤████
└┬┬┬┬▶
1234
Input:
plot(
[
[1, 1],
[2, 4],
[3, 40],
[4, 2],
[5, -1],
[6, 3],
[7, -1],
[8, -1],
[9, 9],
[10, 9],
],
{ width: 40, height: 10 },
);
Output:
▲
40┤ ┏━━━┓
│ ┃ ┃
│ ┃ ┃
│ ┃ ┃
│ ┃ ┃
│ ┃ ┃
│ ┃ ┃
9┤ ┃ ┃ ┏━━━━━
3┤ ┏━━━━┛ ┗━━━┓ ┏━━━┓ ┃
-1┤━━━┛ ┗━━━━┛ ┗━━━━━━━━┛
└┬───┬────┬───┬───┬────┬───┬───┬────┬───┬▶
1 2 3 4 5 6 7 8 9 10
Input:
plot(
[
[1, 1],
[2, 4],
[3, 4],
[4, 2],
[5, -1],
[6, 3],
[7, -1],
[8, 9],
],
{
width: 40,
thresholds: [
{
y: 5,
x: 5,
},
{
x: 2,
},
],
},
);
Output:
▲ ┃ ┃
9┤ ┃ ┃ ┏━
│ ┃ ┃ ┃
│ ┃ ┃ ┃
│━━━━━┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
│ ┃ ┃ ┃
4┤ ┃━━━━━━━━━━┓ ┃ ┃
3┤ ┃ ┃ ┃ ┏━━━━┓ ┃
2┤ ┃ ┗━━━━┃ ┃ ┃ ┃
1┤━━━━━┃ ┃ ┃ ┃ ┃
│ ┃ ┃ ┃ ┃ ┃
-1┤ ┃ ┃━━━━━┛ ┗━━━━━┛
└┬─────┬────┬─────┬────┬─────┬────┬─────┬▶
1 2 3 4 5 6 7 8
Input:
plot(
[
[
[0, 18],
[1, 1],
[2, 3],
[3, 11],
[4, 5],
[5, 16],
[6, 17],
[7, 14],
[8, 7],
[9, 4],
],
[
[0, 0],
[1, 1],
[2, 1],
[3, 1],
[4, 1],
[5, 0],
[6, 1],
[7, 0],
[8, 1],
[9, 0],
],
],
{ width: 40, height: 10, color: ['ansiBlue', 'ansiGreen'] },
);
Output:
▲
17┤━━━━┓ ┏━━━━┓
16┤ ┃ ┏━━━━━┛ ┃
14┤ ┃ ┃ ┗━━━━━┓
11┤ ┃ ┏━━━━━┓ ┃ ┃
│ ┃ ┃ ┃ ┃ ┃
7┤ ┃ ┃ ┃ ┃ ┗━━━━┓
5┤ ┃ ┃ ┗━━━━┛ ┃
4┤ ┃ ┏━━━━┛ ┗━
1┤ ┏━━━━━━━━━━━━━━━━━━━━━┓ ┏━━━━┓ ┏━━━━┓
0┤━━━━┛ ┗━━━━━┛ ┗━━━━━┛ ┗━
└┬────┬─────┬────┬─────┬────┬─────┬────┬─────┬────┬▶
0 1 2 3 4 5 6 7 8 9
Input:
plot(
[
[
[0, -10],
[1, 0.001],
[2, 10],
[3, 200],
[4, 10000],
[5, 2000000],
[6, 50000000],
],
],
{
width: 30,
height: 20,
formatter: (n: number, { axis }: FormatterHelpers) => {
const labels = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
if (axis === 'y') return n;
return labels[n] || 'X';
},
},
);
Output:
▲
50000000┤ ┏━
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
2000000┤ ┏━━━━┛
-10┤━━━━━━━━━━━━━━━━━━━━━━━┛
└┬────┬────┬────┬───┬────┬────┬▶
A B C D E F G
Input:
plot(
[
[
[-8, -8],
[-4, -4],
[-3, -3],
[-2, -2],
[-1, -1],
[0, 0],
[2, 2],
[3, 3],
[4, 4],
[8, 8],
],
],
{ width: 60, height: 20, axisCenter: [0, 0] },
);
Output:
▲
8┤ ┏━
│ ┃
│ ┃
│ ┃
│ ┃
4┤ ┏━━━━━━━━━━━━━━┛
3┤ ┏━━┛
2┤ ┏━━━┛
│ ┃
┬──────────────┬──┬───┬───┬───0│─────────┬──┬──────────────┬─▶
-8 -4 -3 -2 -1 0│ 2 3 4 8
┏━━-1┤
┏━━━┛ -2┤
┏━━━┛ -3┤
┏━━┛ -4┤
┃ │
┃ │
┃ │
┃ │
━━━━━━━━━━━━━━┛ -8┤
│
Input:
plot(
[
[1, 2],
[2, 0],
[3, 5],
[4, 2],
[5, -2],
[6, 3],
],
{
symbols: {
empty: 'x',
empty: '-',
axis: {
n: 'A',
ns: 'i',
y: 't',
nse: 'o',
x: 'j',
we: 'm',
e: 'B',
},
chart: {
we: '1',
wns: '2',
ns: '3',
nse: '4',
wsn: '5',
sne: '6',
},
},
width: 40,
height: 10,
},
);
Output:
xxA-----------------------------------------
x5t---------------61111112------------------
xxi---------------3------3------------------
xxi---------------3------3------------------
x3t---------------3------3---------------61-
x2t11111112-------3------411111112-------3--
xxi-------3-------3--------------3-------3--
x0t-------411111115--------------3-------3--
xxi------------------------------3-------3--
xxi------------------------------3-------3--
-2t------------------------------411111115--
xxojmmmmmmmjmmmmmmmjmmmmmmjmmmmmmmjmmmmmmmjB
xxx1xxxxxxx2xxxxxxx3xxxxxx4xxxxxxx5xxxxxxx6x
Input:
plot(
[
[-5, 2],
[2, -3],
[13, 0.1],
[4, 2],
[5, -2],
[6, 12],
],
{
width: 40,
height: 10,
hideYAxis: true,
hideXAxis: true,
},
);
Output:
┏━━━━━━━━━━━━━━┓
┃ ┃
┃ ┃
┃ ┃
┃ ┃
┃ ┃
━━━━━━━━━━━━━━┓ ┏━┓ ┃ ┃
┃ ┃ ┃ ┃ ┗━
┃ ┃ ┗━┛
┗━━━━┛
Input:
plot(
[
[-9000, 2000],
[-8000, -3000],
[-2000, -2000],
[2000, 2000],
[3000, 1500],
[4000, 5000],
[10000, 1400],
[11000, 20000],
[12000, 30000],
],
{
width: 60,
height: 20,
},
);
Output:
▲
30k┤ ┏━
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
20k┤ ┏━━┛
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
│ ┃
5k┤ ┏━━━━━━━━━━━━━━━┓ ┃
│ ┃ ┃ ┃
1.4k┤━━┓ ┏━━━━━┛ ┗━━┛
│ ┃ ┃
-2k┤ ┃ ┏━━━━━━━━━━┛
-3k┤ ┗━━━━━━━━━━━━━━━━┛
└┬──┬────────────────┬──────────┬──┬──┬───────────────┬──┬──┬▶
-8k 2k 4k 11k
-9k -2k 3k 10k 12k
Input:
plot(
[
[1, 0],
[2, 20],
[3, 29],
[4, 10],
[5, 3],
[6, 40],
[7, 0],
[8, 20],
],
{
height: 10,
width: 30,
lineFormatter: ({ y, plotX, plotY, input, index }) => {
const output = [{ x: plotX, y: plotY, symbol: '█' }];
if (input[index - 1]?.[1] < y) {
return [...output, { x: plotX, y: plotY - 1, symbol: '▲' }];
}
return [...output, { x: plotX, y: plotY + 1, symbol: '▼' }];
},
},
);
Output:
▲ ▲
40┤ █
│ ▲
29┤ █
│ ▲ ▲
20┤ █ █
│
│
10┤ █
3┤ ▼ █
0┤█ ▼ █
└┬───┬───┬───┬────┬───┬───┬───┬▶
1 2 3 4 5 6 7 8