#199, added behaveLikeLine and fillOpacity options, refactored morris.line to separate line and point draw operation

This commit is contained in:
Marcin Chwedziak 2013-03-31 20:16:35 +02:00
parent 0cbd83371f
commit 3c9fff0aa0
7 changed files with 181 additions and 69 deletions

View File

@ -0,0 +1,31 @@
<!doctype html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://raw.github.com/DmitryBaranovskiy/raphael/300aa589f5a0ba7fce667cd62c7cdda0bd5ad904/raphael-min.js"></script>
<script src="../morris.js"></script>
<script src="lib/prettify.js"></script>
<script src="lib/example.js"></script>
<link rel="stylesheet" href="lib/example.css">
<link rel="stylesheet" href="lib/prettify.css">
<link rel="stylesheet" href="../morris.css">
</head>
<body>
<h1>Area charts behaving like line charts</h1>
<div id="graph"></div>
<pre id="code" class="prettyprint linenums">
// Use Morris.Area instead of Morris.Line
Morris.Area({
element: 'graph',
behaveLikeLine: true,
data: [
{x: '2011 Q1', y: 3, z: 3},
{x: '2011 Q2', y: 2, z: 1},
{x: '2011 Q3', y: 2, z: 4},
{x: '2011 Q4', y: 3, z: 3}
],
xkey: 'x',
ykeys: ['y', 'z'],
labels: ['Y', 'Z']
});
</pre>
</body>

View File

@ -19,7 +19,7 @@ Morris.Area({
data: [ data: [
{x: '2011 Q1', y: 3, z: 3}, {x: '2011 Q1', y: 3, z: 3},
{x: '2011 Q2', y: 2, z: 0}, {x: '2011 Q2', y: 2, z: 0},
{x: '2011 Q3', y: 0, z: 2}, {x: '2011 Q3', y: 2, z: 5},
{x: '2011 Q4', y: 4, z: 4} {x: '2011 Q4', y: 4, z: 4}
], ],
xkey: 'x', xkey: 'x',

View File

@ -1,10 +1,20 @@
class Morris.Area extends Morris.Line class Morris.Area extends Morris.Line
# Initialise # Initialise
# #
areaDefaults =
fillOpacity: 'auto'
behaveLikeLine: false
constructor: (options) -> constructor: (options) ->
return new Morris.Area(options) unless (@ instanceof Morris.Area) return new Morris.Area(options) unless (@ instanceof Morris.Area)
@cumulative = true areaOptions = $.extend {}, areaDefaults, options
super(options)
@cumulative = not areaOptions.behaveLikeLine
if areaOptions.fillOpacity is 'auto'
areaOptions.fillOpacity = if areaOptions.behaveLikeLine then .8 else 1
super(areaOptions)
# calculate series data point coordinates # calculate series data point coordinates
# #
@ -14,29 +24,43 @@ class Morris.Area extends Morris.Line
row._x = @transX(row.x) row._x = @transX(row.x)
total = 0 total = 0
row._y = for y in row.y row._y = for y in row.y
total += (y || 0) if @options.behaveLikeLine
@transY(total) @transY(y)
row._ymax = row._y[row._y.length - 1] else
total += (y || 0)
@transY(total)
row._ymax = Math.max.apply Math, row._y
# draw the data series # draw the data series
# #
# @private # @private
drawSeries: -> drawSeries: ->
for i in [@options.ykeys.length-1..0] @seriesPoints = []
path = @paths[i] if @options.behaveLikeLine
if path isnt null range = [0..@options.ykeys.length-1]
path = path + "L#{@transX(@xmax)},#{@bottom}L#{@transX(@xmin)},#{@bottom}Z" else
@drawFilledPath(path, @fillForSeries(i)) range = [@options.ykeys.length-1..0]
super()
for i in range
@_drawFillFor i
@_drawLineFor i
@_drawPointFor i
_drawFillFor: (index) ->
path = @paths[index]
if path isnt null
path = path + "L#{@transX(@xmax)},#{@bottom}L#{@transX(@xmin)},#{@bottom}Z"
@drawFilledPath path, @fillForSeries(index)
fillForSeries: (i) -> fillForSeries: (i) ->
color = Raphael.rgb2hsl @colorFor(@data[i], i, 'line') color = Raphael.rgb2hsl @colorFor(@data[i], i, 'line')
Raphael.hsl( Raphael.hsl(
color.h, color.h,
Math.min(255, color.s * 0.75), Math.min(255, if @options.behaveLikeLine then color.s * 0.9 else color.s * 0.75),
Math.min(255, color.l * 1.25)) Math.min(255, if @options.behaveLikeLine then color.l * 1.2 else color.l * 1.25))
drawFilledPath: (path, fill) -> drawFilledPath: (path, fill) ->
@raphael.path(path) @raphael.path(path)
.attr('fill', fill) .attr('fill', fill)
.attr('fill-opacity', @options.fillOpacity)
.attr('stroke-width', 0) .attr('stroke-width', 0)

View File

@ -166,18 +166,24 @@ class Morris.Line extends Morris.Grid
# #
# @private # @private
drawSeries: -> drawSeries: ->
@seriesPoints = []
for i in [@options.ykeys.length-1..0] for i in [@options.ykeys.length-1..0]
path = @paths[i] @_drawLineFor i
if path isnt null
@drawLinePath(path, @colorFor(row, i, 'line')) #row isn't available here?
@seriesPoints = ([] for i in [0...@options.ykeys.length])
for i in [@options.ykeys.length-1..0] for i in [@options.ykeys.length-1..0]
for row in @data @_drawPointFor i
if row._y[i]?
circle = @drawLinePoint(row._x, row._y[i], @options.pointSize, @colorFor(row, i, 'point'), i) _drawPointFor: (index) ->
else @seriesPoints[index] = []
circle = null for row in @data
@seriesPoints[i].push(circle) circle = null
if row._y[index]?
circle = @drawLinePoint(row._x, row._y[index], @options.pointSize, @colorFor(row, index, 'point'), index)
@seriesPoints[index].push(circle)
_drawLineFor: (index) ->
path = @paths[index]
if path isnt null
@drawLinePath path, @colorFor(null, index, 'line')
# create a path for a data series # create a path for a data series
# #

124
morris.js
View File

@ -793,42 +793,42 @@
}; };
Line.prototype.drawSeries = function() { Line.prototype.drawSeries = function() {
var circle, i, path, row, _i, _j, _ref, _ref1, _results; var i, _i, _j, _ref, _ref1, _results;
this.seriesPoints = [];
for (i = _i = _ref = this.options.ykeys.length - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) { for (i = _i = _ref = this.options.ykeys.length - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) {
path = this.paths[i]; this._drawLineFor(i);
if (path !== null) {
this.drawLinePath(path, this.colorFor(row, i, 'line'));
}
} }
this.seriesPoints = (function() {
var _j, _ref1, _results;
_results = [];
for (i = _j = 0, _ref1 = this.options.ykeys.length; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
_results.push([]);
}
return _results;
}).call(this);
_results = []; _results = [];
for (i = _j = _ref1 = this.options.ykeys.length - 1; _ref1 <= 0 ? _j <= 0 : _j >= 0; i = _ref1 <= 0 ? ++_j : --_j) { for (i = _j = _ref1 = this.options.ykeys.length - 1; _ref1 <= 0 ? _j <= 0 : _j >= 0; i = _ref1 <= 0 ? ++_j : --_j) {
_results.push((function() { _results.push(this._drawPointFor(i));
var _k, _len, _ref2, _results1;
_ref2 = this.data;
_results1 = [];
for (_k = 0, _len = _ref2.length; _k < _len; _k++) {
row = _ref2[_k];
if (row._y[i] != null) {
circle = this.drawLinePoint(row._x, row._y[i], this.options.pointSize, this.colorFor(row, i, 'point'), i);
} else {
circle = null;
}
_results1.push(this.seriesPoints[i].push(circle));
}
return _results1;
}).call(this));
} }
return _results; return _results;
}; };
Line.prototype._drawPointFor = function(index) {
var circle, row, _i, _len, _ref, _results;
this.seriesPoints[index] = [];
_ref = this.data;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
row = _ref[_i];
circle = null;
if (row._y[index] != null) {
circle = this.drawLinePoint(row._x, row._y[index], this.options.pointSize, this.colorFor(row, index, 'point'), index);
}
_results.push(this.seriesPoints[index].push(circle));
}
return _results;
};
Line.prototype._drawLineFor = function(index) {
var path;
path = this.paths[index];
if (path !== null) {
return this.drawLinePath(path, this.colorFor(null, index, 'line'));
}
};
Line.createPath = function(coords, smooth, bottom) { Line.createPath = function(coords, smooth, bottom) {
var coord, g, grads, i, ix, lg, path, prevCoord, x1, x2, y1, y2, _i, _len; var coord, g, grads, i, ix, lg, path, prevCoord, x1, x2, y1, y2, _i, _len;
path = ""; path = "";
@ -1079,15 +1079,26 @@
Morris.AUTO_LABEL_ORDER = ["decade", "year", "month", "day", "hour", "30min", "15min", "10min", "5min", "minute", "30sec", "15sec", "10sec", "5sec", "second"]; Morris.AUTO_LABEL_ORDER = ["decade", "year", "month", "day", "hour", "30min", "15min", "10min", "5min", "minute", "30sec", "15sec", "10sec", "5sec", "second"];
Morris.Area = (function(_super) { Morris.Area = (function(_super) {
var areaDefaults;
__extends(Area, _super); __extends(Area, _super);
areaDefaults = {
fillOpacity: 'auto',
behaveLikeLine: false
};
function Area(options) { function Area(options) {
var areaOptions;
if (!(this instanceof Morris.Area)) { if (!(this instanceof Morris.Area)) {
return new Morris.Area(options); return new Morris.Area(options);
} }
this.cumulative = true; areaOptions = $.extend({}, areaDefaults, options);
Area.__super__.constructor.call(this, options); this.cumulative = !areaOptions.behaveLikeLine;
if (areaOptions.fillOpacity === 'auto') {
areaOptions.fillOpacity = areaOptions.behaveLikeLine ? .8 : 1;
}
Area.__super__.constructor.call(this, areaOptions);
} }
Area.prototype.calcPoints = function() { Area.prototype.calcPoints = function() {
@ -1104,36 +1115,63 @@
_results1 = []; _results1 = [];
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
y = _ref1[_j]; y = _ref1[_j];
total += y || 0; if (this.options.behaveLikeLine) {
_results1.push(this.transY(total)); _results1.push(this.transY(y));
} else {
total += y || 0;
_results1.push(this.transY(total));
}
} }
return _results1; return _results1;
}).call(this); }).call(this);
_results.push(row._ymax = row._y[row._y.length - 1]); _results.push(row._ymax = Math.max.apply(Math, row._y));
} }
return _results; return _results;
}; };
Area.prototype.drawSeries = function() { Area.prototype.drawSeries = function() {
var i, path, _i, _ref; var i, range, _i, _j, _k, _len, _ref, _ref1, _results, _results1, _results2;
for (i = _i = _ref = this.options.ykeys.length - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) { this.seriesPoints = [];
path = this.paths[i]; if (this.options.behaveLikeLine) {
if (path !== null) { range = (function() {
path = path + ("L" + (this.transX(this.xmax)) + "," + this.bottom + "L" + (this.transX(this.xmin)) + "," + this.bottom + "Z"); _results = [];
this.drawFilledPath(path, this.fillForSeries(i)); for (var _i = 0, _ref = this.options.ykeys.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; 0 <= _ref ? _i++ : _i--){ _results.push(_i); }
} return _results;
}).apply(this);
} else {
range = (function() {
_results1 = [];
for (var _j = _ref1 = this.options.ykeys.length - 1; _ref1 <= 0 ? _j <= 0 : _j >= 0; _ref1 <= 0 ? _j++ : _j--){ _results1.push(_j); }
return _results1;
}).apply(this);
}
_results2 = [];
for (_k = 0, _len = range.length; _k < _len; _k++) {
i = range[_k];
this._drawFillFor(i);
this._drawLineFor(i);
_results2.push(this._drawPointFor(i));
}
return _results2;
};
Area.prototype._drawFillFor = function(index) {
var path;
path = this.paths[index];
if (path !== null) {
path = path + ("L" + (this.transX(this.xmax)) + "," + this.bottom + "L" + (this.transX(this.xmin)) + "," + this.bottom + "Z");
return this.drawFilledPath(path, this.fillForSeries(index));
} }
return Area.__super__.drawSeries.call(this);
}; };
Area.prototype.fillForSeries = function(i) { Area.prototype.fillForSeries = function(i) {
var color; var color;
color = Raphael.rgb2hsl(this.colorFor(this.data[i], i, 'line')); color = Raphael.rgb2hsl(this.colorFor(this.data[i], i, 'line'));
return Raphael.hsl(color.h, Math.min(255, color.s * 0.75), Math.min(255, color.l * 1.25)); return Raphael.hsl(color.h, Math.min(255, this.options.behaveLikeLine ? color.s * 0.9 : color.s * 0.75), Math.min(255, this.options.behaveLikeLine ? color.l * 1.2 : color.l * 1.25));
}; };
Area.prototype.drawFilledPath = function(path, fill) { Area.prototype.drawFilledPath = function(path, fill) {
return this.raphael.path(path).attr('fill', fill).attr('stroke-width', 0); return this.raphael.path(path).attr('fill', fill).attr('fill-opacity', this.options.fillOpacity).attr('stroke-width', 0);
}; };
return Area; return Area;

2
morris.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -42,6 +42,19 @@ describe 'Morris.Area', ->
gridTextColor: '#888' gridTextColor: '#888'
gridTextSize: 12 gridTextSize: 12
it 'should not be cumulative if behaveLikeLine', ->
chart = Morris.Area $.extend {}, defaults, behaveLikeLine: true
chart.cumulative.should.equal false
it 'should have a line with transparent fill if behaveLikeLine', ->
chart = Morris.Area $.extend {}, defaults, behaveLikeLine: true
$('#graph').find("path[fill-opacity='0.8']").size().should.equal 1
it 'should not have a line with transparent fill', ->
chart = Morris.Area $.extend {}, defaults
$('#graph').find("path[fill-opacity='0.8']").size().should.equal 0
it 'should have a line with the fill of a modified line color', -> it 'should have a line with the fill of a modified line color', ->
chart = Morris.Area $.extend {}, defaults chart = Morris.Area $.extend {}, defaults
$('#graph').find("path[fill='#2577b5']").size().should.equal 1 $('#graph').find("path[fill='#0b62a4']").size().should.equal 0
$('#graph').find("path[fill='#7a92a3']").size().should.equal 0