Morris donut chart always start at 12 oclock (top middle)

2.4k Views Asked by At

I am using Morris donut charts and I want to try and ensure that the segments always start at 12 o'clock so there is some consistency when viewing multiple charts together on the same screen.

Previously I used jQuery circliful https://github.com/pguso/jquery-plugin-circliful which worked great but the quality was really fuzzy on Retina screens so I checked out Morris and it does what I want bar this.

I want something that looks like the first where the segment always starts at the top middle:

Circliful Morris

Does anyone know how to achieve this?

var donut= Morris.Donut({
  element: 'donut-example',
  data: [
    {label: "New clients", value: 35},
    {label: "In-Store Sales", value: 65}
  ],
  colors: ['#90c070', '#eeeeee'],
  select :0   
});

donut.select(0);

http://jsfiddle.net/0t976ez6/

1

There are 1 best solutions below

0
On

No, as far as I can tell from the source code, there's no API (public or private) in Morris to do that. See Donut's redraw() for how it renders the chart: it always starts from the lowest point and goes in counterclockwise direction.

Now, I would recommend to consider switching to other chart libraries, before proceeding. For example there's a D3.js — not the simplest library out there, but it provides so much freedom to do almost anything, it sometimes may be hard to work with. But there is a ton of examples of every thing you could think of. Like, here's a blog post about creating similar looking donut charts using D3.

But hey, it's a JavaScript, we can patch things, so nothing is impossible!

Warning: patching library code is not the best idea! You must be careful and do that only when it's really the only way.

The easiest (certainly not the simplest) way would be to monkey-patch Morris.Donut.redraw() to start from the Math.PI (middle top) and go in clockwise direction. To do that, the initial value of last local variable should be changed from 0 to Math.PI and logic that finds next starting point tweaked a bit.

Here's is a working example where I've surrounded lines that were patched with special PATCHED comments:

/**
 * Careful: patched lib-function!
 *
 * In case of problems, see <a href="https://github.com/morrisjs/morris.js/blob/master/morris.js#L1894">original</a>
 */
Morris.Donut.prototype.redraw = function() {
  var C, cx, cy, i, idx, last, max_value, min, next, seg, total, value, w, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results;
  this.raphael.clear();
  cx = this.el.width() / 2;
  cy = this.el.height() / 2;
  w = (Math.min(cx, cy) - 10) / 3;
  total = 0;
  _ref = this.values;
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
    value = _ref[_i];
    total += value;
  }
  min = 5 / (2 * w);
  C = 1.9999 * Math.PI - min * this.data.length;
  // ------------ PATCHED: start from the top
  last = Math.PI;
  // original:
  // last = 0
  // ------------
  idx = 0;
  this.segments = [];
  _ref1 = this.values;
  for (i = _j = 0, _len1 = _ref1.length; _j < _len1; i = ++_j) {
    value = _ref1[i];
    // ------------ PATCHED: change direction of rendering
    next = last - min - C * (value / total);
    seg = new Morris.DonutSegment(cx, cy, w * 2, w, next, last, this.data[i].color || this.options.colors[idx % this.options.colors.length], this.options.backgroundColor, idx, this.raphael);
    // original:
    // next = last + min + C * (value / total);
    // seg = new Morris.DonutSegment(cx, cy, w * 2, w, last, next, this.data[i].color || this.options.colors[idx % this.options.colors.length], this.options.backgroundColor, idx, this.raphael);
    // ------------
    seg.render();
    this.segments.push(seg);
    seg.on('hover', this.select);
    seg.on('click', this.click);
    last = next;
    idx += 1;
  }
  this.text1 = this.drawEmptyDonutLabel(cx, cy - 10, this.options.labelColor, 15, 800);
  this.text2 = this.drawEmptyDonutLabel(cx, cy + 10, this.options.labelColor, 14);
  max_value = Math.max.apply(Math, this.values);
  idx = 0;
  _ref2 = this.values;
  _results = [];
  for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
    value = _ref2[_k];
    if (value === max_value) {
      this.select(idx);
      break;
    }
    _results.push(idx += 1);
  }
  return _results;
};

var donut = Morris.Donut({
  element: 'donut-example',
  data: [{
    label: "In-Store Sales",
    value: 55
  }, {
    label: "New clients",
    value: 25
  }, {
    label: "Reseller stores",
    value: 20
  }],
  colors: ['#90c070', '#ee9090', '#9090ee'],
  select: 0
});

donut.select(0);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://rawgit.com/morrisjs/morris.js/master/morris.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<link href="http://cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css" rel="stylesheet" />
<div id="donut-example" style="height: 250px;"></div>

Of course, there's may be another way to achieve the same thing: maybe I overlooked some local variable changing sign of which will result in the same rendering.