1.0.1 β€’ Published 8 months ago

simple-ascii-chart-cli v1.0.1

Weekly downloads
-
License
MIT
Repository
github
Last release
8 months ago

Simple ascii chart

NPM License NPM Version npm package minimized gzipped size (select exports) Codecov

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.

Interactive demo.

With colored multiline:

Example chart

With colored area:

Views per iteration

With axis:

Example chart with center position

Usage

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);

Playground

Alternatively, you can create a graph interactively in the playground.

API Endpoint

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}

How to use it

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

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],
  ],
];

Settings

Plot can be adjusted with a second parameter settings.

color

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'];

width

Changes default width of the graph, can be used to scale up/down values:

width?: number

height

Changes default height of the graph, can be used to scale up/down values:

height?: number

axisCenter

Changes center of the axis, by default it's placed in the bottom-left:

axisCenter?: [x:number, y:number]

formatter

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;
};

lineFormatter

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.

title

Adds title above the graph:

title?: string;

xLabel

Adds label to the x axis:

xLabe
l?: string;

yLabel

Adds label to the y axis:

yLabel?: string;

thresholds

Adds thresholds to plot:

thresholds?: {
    x?: number;
    y?: number;
    color?: Color;
  }[];

fillArea

Some graphs look better presented as a area, not lines. In order to use area chart, pass fillArea prop:

fillArea?: boolean;

hideXAxis

Hide X axis:

hideXAxis? boolean;

hideYAxis

Hide Y axis:

hideYAxis? boolean;

symbols

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: 'β–ˆ'
    }

Summary

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: 'β–ˆ',
      }
    }
};

Examples

Simple plot

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

Plot with title and defined size

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

Plot with axis labels

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

Plot with colors

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

Plot with borders

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           β–ˆ
β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ

Plot with filled area

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

Scaled up plot

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

Add thresholds

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

Multi-series plot

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

Plot with formatting applied

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

Plot with axis center

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─
                                 β”‚

Plot with custom symbols

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

Plot without axis

Input:

plot(
  [
    [-5, 2],
    [2, -3],
    [13, 0.1],
    [4, 2],
    [5, -2],
    [6, 12],
  ],
  {
    width: 40,
    height: 10,
    hideYAxis: true,
    hideXAxis: true,
  },
);

Output:

                           ┏━━━━━━━━━━━━━━┓
                           ┃              ┃
                           ┃              ┃
                           ┃              ┃
                           ┃              ┃
                           ┃              ┃
    ━━━━━━━━━━━━━━┓    ┏━┓ ┃              ┃
                  ┃    ┃ ┃ ┃              ┗━
                  ┃    ┃ ┗━┛
                  ┗━━━━┛

Plot with with large numbers

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

Plot with custom line format

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
1.0.1

8 months ago

1.0.0

8 months ago