kroitor / asciichart

Nice-looking lightweight console ASCII line charts ╭┈╯ for NodeJS, browsers and terminal, no dependencies
MIT License
1.83k stars 94 forks source link

Fixed y-axis #40

Open ghost opened 4 years ago

ghost commented 4 years ago

Hey, I extended this thing to allow a fixed y-axis, instead of computing the bounds automatically. Just add min- and max fields to the config object. The data will be clamped in case it goes out of bounds. Feel free to commit.


"use strict";

(function (exports) {

    exports.plot = function (series, cfg = undefined) {
        cfg         = (typeof cfg !== 'undefined') ? cfg : {}
        let min = cfg.min;
        let max = cfg.max;
        if (min === undefined || max === undefined) {
            min = series[0]
            max = series[0]
            for (let i = 1; i < series.length; i++) {
                min = Math.min (min, series[i])
                max = Math.max (max, series[i])
            }
        } else {
            // clamp values to min, max
            for (let i = 0; i < series.length; i++) {
                series[i] = Math.max(min, Math.min(max, series[i]))
            }
        }
        let range   = Math.abs (max - min)
        let offset  = (typeof cfg.offset  !== 'undefined') ? cfg.offset  : 3
        let padding = (typeof cfg.padding !== 'undefined') ? cfg.padding : '           '
        let height  = (typeof cfg.height  !== 'undefined') ? cfg.height  : range
        let ratio   = range !== 0 ? height / range : 1;
        let min2    = Math.round (min * ratio)
        let max2    = Math.round (max * ratio)
        let rows    = Math.abs (max2 - min2)
        let width   = series.length + offset
        let format  = (typeof cfg.format !== 'undefined') ? cfg.format : function (x) {
            return (padding + x.toFixed (2)).slice (-padding.length)
        }
        let result = new Array (rows + 1) // empty space
        for (let i = 0; i <= rows; i++) {
            result[i] = new Array (width)
            for (let j = 0; j < width; j++) {
                result[i][j] = ' '
            }
        }
        for (let y = min2; y <= max2; ++y) { // axis + labels
            let label = format (rows > 0 ? max - (y - min2) * range / rows : y, y - min2)
            result[y - min2][Math.max (offset - label.length, 0)] = label
            result[y - min2][offset - 1] = (y == 0) ? '┼' : '┤'
        }

        let y0 = Math.round (series[0] * ratio) - min2
        result[rows - y0][offset - 1] = '┼' // first value

        for (let x = 0; x < series.length - 1; x++) { // plot the line
            let y0 = Math.round((series[x + 0] * ratio) - min2)
            let y1 = Math.round((series[x + 1] * ratio) - min2)
            if (y0 == y1) {
                result[rows - y0][x + offset] = '─'
            } else {
                result[rows - y1][x + offset] = (y0 > y1) ? '╰' : '╭'
                result[rows - y0][x + offset] = (y0 > y1) ? '╮' : '╯'
                let from = Math.min (y0, y1)
                let to = Math.max (y0, y1)
                for (let y = from + 1; y < to; y++) {
                    result[rows - y][x + offset] = '│'
                }
            }
        }

        return result.map (function (x) { return x.join ('') }).join ('\n')
    }

}) (typeof exports === 'undefined' ? /* istanbul ignore next */ this['asciichart'] = {} : exports);
kroitor commented 4 years ago

@vuoriov4 hey ) thx a lot for your involvement! appreciate it! would you be so kind to submit a PR? ) i'll do my best to get to it as soon as i can!

jamesgpearce commented 4 years ago

Offering up something similar in #48