Logarithmic y axis with morris.js

2019-04-11 14:41发布

问题:

I'm trying to get a logarithmic scale for the y-axis of a morris.js line chart.

http://www.oesmith.co.uk/morris.js/lines.html

I already tried playing with the yLabelFormat option, but it's not what I need. Any hint is appreciated.

If there is no way of doing this with morris.js, you can suggest another lightweight javascript library to make simple line charts with logarithmic scale.

回答1:

You can extend Morris and modify the transY function to do the logarithmic scale.

I also added the gridIntegers parameter to have only integers on the y-Axis.

Remove the code after the transY function if you want only the yLogScale.

Use the following snippet to view the result with yLogScale parameter set as true or false.

(function () {
    var $, MyMorris;

    MyMorris = window.MyMorris = {};
    $ = jQuery;

    MyMorris = Object.create(Morris);

    MyMorris.Grid.prototype.gridDefaults["yLogScale"] = false;
    MyMorris.Grid.prototype.gridDefaults["gridIntegers"] = false;

    MyMorris.Grid.prototype.transY = function (y) {
        if (!this.options.horizontal) {
            if (this.options.yLogScale) {
                return this.bottom - (this.height * Math.log((y + 1) - this.ymin) / Math.log(this.ymax / (this.ymin + 1)));
            } else {
                return this.bottom - (y - this.ymin) * this.dy;
            }
        } else {
            return this.left + (y - this.ymin) * this.dy;
        }
    };

    MyMorris.Grid.prototype.setData = function (data, redraw) {
        var e, idx, index, maxGoal, minGoal, ret, row, step, total, y, ykey, ymax, ymin, yval, _ref;
        if (redraw == null) {
            redraw = true;
        }
        this.options.data = data;
        if ((data == null) || data.length === 0) {
            this.data = [];
            this.raphael.clear();
            if (this.hover != null) {
                this.hover.hide();
            }
            return;
        }
        ymax = this.cumulative ? 0 : null;
        ymin = this.cumulative ? 0 : null;
        if (this.options.goals.length > 0) {
            minGoal = Math.min.apply(Math, this.options.goals);
            maxGoal = Math.max.apply(Math, this.options.goals);
            ymin = ymin != null ? Math.min(ymin, minGoal) : minGoal;
            ymax = ymax != null ? Math.max(ymax, maxGoal) : maxGoal;
        }
        this.data = (function () {
            var _i, _len, _results;
            _results = [];
            for (index = _i = 0, _len = data.length; _i < _len; index = ++_i) {
                row = data[index];
                ret = {
                    src: row
                };
                ret.label = row[this.options.xkey];
                if (this.options.parseTime) {
                    ret.x = Morris.parseDate(ret.label);
                    if (this.options.dateFormat) {
                        ret.label = this.options.dateFormat(ret.x);
                    } else if (typeof ret.label === 'number') {
                        ret.label = new Date(ret.label).toString();
                    }
                } else {
                    ret.x = index;
                    if (this.options.xLabelFormat) {
                        ret.label = this.options.xLabelFormat(ret);
                    }
                }
                total = 0;
                ret.y = (function () {
                    var _j, _len1, _ref, _results1;
                    _ref = this.options.ykeys;
                    _results1 = [];
                    for (idx = _j = 0, _len1 = _ref.length; _j < _len1; idx = ++_j) {
                        ykey = _ref[idx];
                        yval = row[ykey];
                        if (typeof yval === 'string') {
                            yval = parseFloat(yval);
                        }
                        if ((yval != null) && typeof yval !== 'number') {
                            yval = null;
                        }
                        if (yval != null) {
                            if (this.cumulative) {
                                total += yval;
                            } else {
                                if (ymax != null) {
                                    ymax = Math.max(yval, ymax);
                                    ymin = Math.min(yval, ymin);
                                } else {
                                    ymax = ymin = yval;
                                }
                            }
                        }
                        if (this.cumulative && (total != null)) {
                            ymax = Math.max(total, ymax);
                            ymin = Math.min(total, ymin);
                        }
                        _results1.push(yval);
                    }
                    return _results1;
                }).call(this);
                _results.push(ret);
            }
            return _results;
        }).call(this);
        if (this.options.parseTime) {
            this.data = this.data.sort(function (a, b) {
                return (a.x > b.x) - (b.x > a.x);
            });
        }
        this.xmin = this.data[0].x;
        this.xmax = this.data[this.data.length - 1].x;
        this.events = [];
        if (this.options.events.length > 0) {
            if (this.options.parseTime) {
                this.events = (function () {
                    var _i, _len, _ref, _results;
                    _ref = this.options.events;
                    _results = [];
                    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                        e = _ref[_i];
                        _results.push(Morris.parseDate(e));
                    }
                    return _results;
                }).call(this);
            } else {
                this.events = this.options.events;
            }
            this.xmax = Math.max(this.xmax, Math.max.apply(Math, this.events));
            this.xmin = Math.min(this.xmin, Math.min.apply(Math, this.events));
        }
        if (this.xmin === this.xmax) {
            this.xmin -= 1;
            this.xmax += 1;
        }
        this.ymin = this.yboundary('min', ymin);
        this.ymax = this.yboundary('max', ymax);
        if (this.ymin === this.ymax) {
            if (ymin) {
                this.ymin -= 1;
            }
            this.ymax += 1;
        }
        if (((_ref = this.options.axes) === true || _ref === 'both' || _ref === 'y') || this.options.grid === true) {
            if (this.options.ymax === this.gridDefaults.ymax && this.options.ymin === this.gridDefaults.ymin) {
                this.grid = this.autoGridLines(this.ymin, this.ymax, this.options.numLines);
                this.ymin = Math.min(this.ymin, this.grid[0]);
                this.ymax = Math.max(this.ymax, this.grid[this.grid.length - 1]);
            } else {
                step = (this.ymax - this.ymin) / (this.options.numLines - 1);
                if (this.options.gridIntegers) {
                    step = Math.max(1, Math.round(step));
                }
                this.grid = (function () {
                    var _i, _ref1, _ref2, _results;
                    _results = [];
                    for (y = _i = _ref1 = this.ymin, _ref2 = this.ymax; step > 0 ? _i <= _ref2 : _i >= _ref2; y = _i += step) {
                        _results.push(y);
                    }
                    return _results;
                }).call(this);
            }
        }
        this.dirty = true;
        if (redraw) {
            return this.redraw();
        }
    };
}).call(this);

var data = [
    { y: '2016-01', a: 1, b: 5 },
    { y: '2016-02', a: 2,  b: 3 },
    { y: '2016-03', a: 2,  b: 2 },
    { y: '2016-04', a: 1,  b: 1000 },
    { y: '2016-05', a: 2,  b: 2 },
    { y: '2016-06', a: 3,  b: 3 },
    { y: '2016-07', a: 1, b: 2 }
  ];

var morrisArea =
Morris.Area({
    element: 'chart',
    data: data,
  xkey: 'y',
  ykeys: ['a', 'b'],
  labels: ['Label A', 'Label B'],
  fillOpacity: 0.6,
  hideHover: 'auto',
  resize: true,
  yLogScale: true,
  gridIntegers: true,
  pointFillColors: ['#ffffff'],
  pointStrokeColors: ['black'],
  lineColors: ['gray', 'blue'],
  gridIntegers: true,
  ymin: 0
});

$(".button").on("click", function() {
  $(".button").removeClass("on");
  $(this).addClass("on");
});

function setYLogScale(status) {
  morrisArea.options["yLogScale"] = status;
  morrisArea.setData(data);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css" rel="stylesheet" />
<style>
  body { font-family: Arial; }
  .button {
    padding: 3px 5px;
    border: 1px solid black;
    background-color: #eeeeee;
    display: inline-block;
    cursor: pointer;
  }
  .on { background-color: lightblue; }
</style>

<div class="button" onclick="setYLogScale(true);">yLogScale ON</div>
<div class="button" onclick="setYLogScale(false);">yLogScale OFF</div>
<div id="chart"></div>