mirror of
https://github.com/morrisjs/morris.js.git
synced 2024-11-13 07:11:12 +01:00
Merge branch 'coffee-rewrite'
Conflicts: morris.js
This commit is contained in:
commit
9b4791534b
3 changed files with 602 additions and 292 deletions
|
@ -45,7 +45,7 @@
|
|||
{"period": "1995 Q4", "licensed": 1702, "sorned": 0},
|
||||
{"period": "1994 Q4", "licensed": 1732, "sorned": 0}
|
||||
];
|
||||
$('#graph').hml({
|
||||
new Morris.Line("graph", {
|
||||
data: tax_data,
|
||||
xkey: 'period',
|
||||
ykeys: ['licensed', 'sorned'],
|
||||
|
@ -53,4 +53,4 @@
|
|||
});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</body>
|
||||
|
|
298
morris.coffee
Normal file
298
morris.coffee
Normal file
|
@ -0,0 +1,298 @@
|
|||
# The original line graph.
|
||||
#
|
||||
window.Morris = {}
|
||||
class window.Morris.Line
|
||||
|
||||
# Initialise the graph.
|
||||
#
|
||||
# @param {string} id Target element's DOM ID
|
||||
# @param {Object} options
|
||||
constructor: (id, options) ->
|
||||
@el = $ document.getElementById(id)
|
||||
@options = $.extend @defaults, options
|
||||
# bail if there's no data
|
||||
if @options.data is undefined or @options.data.length is 0
|
||||
return
|
||||
@el.addClass 'graph-initialised'
|
||||
@precalc()
|
||||
@redraw()
|
||||
|
||||
# Default configuration
|
||||
#
|
||||
defaults:
|
||||
lineWidth: 3
|
||||
pointSize: 4
|
||||
lineColors: [
|
||||
'#0b62a4'
|
||||
'#7A92A3'
|
||||
'#4da74d'
|
||||
'#afd8f8'
|
||||
'#edc240'
|
||||
'#cb4b4b'
|
||||
'#9440ed'
|
||||
]
|
||||
marginTop: 25
|
||||
marginRight: 25
|
||||
marginBottom: 30
|
||||
marginLeft: 25
|
||||
numLines: 5
|
||||
gridLineColor: '#aaa'
|
||||
gridTextColor: '#888'
|
||||
gridTextSize: 12
|
||||
gridStrokeWidth: 0.5
|
||||
hoverPaddingX: 10
|
||||
hoverPaddingY: 5
|
||||
hoverMargin: 10
|
||||
hoverFillColor: '#fff'
|
||||
hoverBorderColor: '#ccc'
|
||||
hoverBorderWidth: 2
|
||||
hoverOpacity: 0.95
|
||||
hoverLabelColor: '#444'
|
||||
hoverFontSize: 12
|
||||
|
||||
# Do any necessary pre-processing for a new dataset
|
||||
#
|
||||
precalc: ->
|
||||
# extract labels
|
||||
@columnLabels = $.map @options.data, (d) => d[@options.xkey]
|
||||
@seriesLabels = @options.labels
|
||||
|
||||
# extract series data
|
||||
@series = []
|
||||
for ykey in @options.ykeys
|
||||
@series.push $.map @options.data, (d) -> d[ykey]
|
||||
|
||||
# translate x labels into nominal dates
|
||||
# note: currently using decimal years to specify dates
|
||||
@xvals = $.map @columnLabels, (x) => @parseYear x
|
||||
@xmin = Math.min.apply null, @xvals
|
||||
@xmax = Math.max.apply null, @xvals
|
||||
if @xmin is @xmax
|
||||
@xmin -= 1
|
||||
@xmax += 1
|
||||
|
||||
# use $.map to flatten arrays and find the max y value
|
||||
all_y_vals = $.map @series, (x) -> Math.max.apply null, x
|
||||
@ymax = Math.max(20, Math.max.apply(null, all_y_vals))
|
||||
|
||||
# Clear and redraw the graph
|
||||
#
|
||||
redraw: ->
|
||||
# remove child elements (get rid of old drawings)
|
||||
@el.empty()
|
||||
|
||||
# the raphael drawing instance
|
||||
@r = new Raphael(@el[0])
|
||||
|
||||
# calculate grid dimensions
|
||||
left = @measureText(@ymax, @options.gridTextSize).width + @options.marginLeft
|
||||
width = @el.width() - left - @options.marginRight
|
||||
height = @el.height() - @options.marginTop - @options.marginBottom
|
||||
dx = width / (@xmax - @xmin)
|
||||
dy = height / @ymax
|
||||
|
||||
# quick translation helpers
|
||||
transX = (x) =>
|
||||
if @xvals.length is 1
|
||||
left + width / 2
|
||||
else
|
||||
left + (x - @xmin) * dx
|
||||
transY = (y) =>
|
||||
return @options.marginTop + height - y * dy
|
||||
|
||||
# draw y axis labels, horizontal lines
|
||||
lineInterval = height / (@options.numLines - 1)
|
||||
for i in [0..@options.numLines-1]
|
||||
y = @options.marginTop + i * lineInterval
|
||||
v = Math.round((@options.numLines - 1 - i) * @ymax / (@options.numLines - 1))
|
||||
@r.text(left - @options.marginLeft/2, y, v)
|
||||
.attr('font-size', @options.gridTextSize)
|
||||
.attr('fill', @options.gridTextColor)
|
||||
.attr('text-anchor', 'end')
|
||||
@r.path("M" + left + "," + y + 'H' + (left + width))
|
||||
.attr('stroke', @options.gridLineColor)
|
||||
.attr('stroke-width', @options.gridStrokeWidth)
|
||||
|
||||
# draw x axis labels
|
||||
prevLabelMargin = null
|
||||
xLabelMargin = 50 # make this an option?
|
||||
for i in [Math.ceil(@xmin)..Math.floor(@xmax)]
|
||||
label = @r.text(transX(i), @options.marginTop + height + @options.marginBottom / 2, i)
|
||||
.attr('font-size', @options.gridTextSize)
|
||||
.attr('fill', @options.gridTextColor)
|
||||
labelBox = label.getBBox()
|
||||
# ensure a minimum of `xLabelMargin` pixels between labels
|
||||
if prevLabelMargin is null or prevLabelMargin <= labelBox.x
|
||||
prevLabelMargin = labelBox.x + labelBox.width + xLabelMargin
|
||||
else
|
||||
label.remove()
|
||||
|
||||
# draw the actual series
|
||||
columns = (transX(x) for x in @xvals)
|
||||
seriesCoords = []
|
||||
for s in @series
|
||||
seriesCoords.push($.map(s, (y, i) -> x: columns[i], y: transY(y)))
|
||||
for i in [seriesCoords.length-1..0]
|
||||
coords = seriesCoords[i]
|
||||
if coords.length > 1
|
||||
path = @createPath coords, @options.marginTop, left, @options.marginTop + height, left + width
|
||||
@r.path(path)
|
||||
.attr('stroke', @options.lineColors[i])
|
||||
.attr('stroke-width', @options.lineWidth)
|
||||
seriesPoints = ([] for i in [0..seriesCoords.length-1])
|
||||
for i in [seriesCoords.length-1..0]
|
||||
for c in seriesCoords[i]
|
||||
circle = @r.circle(c.x, c.y, @options.pointSize)
|
||||
.attr('fill', @options.lineColors[i])
|
||||
.attr('stroke-width', 1)
|
||||
.attr('stroke', '#ffffff')
|
||||
seriesPoints[i].push(circle)
|
||||
|
||||
# hover labels
|
||||
hoverHeight = @options.hoverFontSize * 1.5 * (@series.length + 1)
|
||||
hover = @r.rect(-10, -hoverHeight / 2 - @options.hoverPaddingY, 20, hoverHeight + @options.hoverPaddingY * 2, 10)
|
||||
.attr('fill', @options.hoverFillColor)
|
||||
.attr('stroke', @options.hoverBorderColor)
|
||||
.attr('stroke-width', @options.hoverBorderWidth)
|
||||
.attr('opacity', @options.hoverOpacity)
|
||||
xLabel = @r.text(0, (@options.hoverFontSize * 0.75) - hoverHeight / 2, '')
|
||||
.attr('fill', @options.hoverLabelColor)
|
||||
.attr('font-weight', 'bold')
|
||||
.attr('font-size', @options.hoverFontSize)
|
||||
hoverSet = @r.set()
|
||||
hoverSet.push(hover)
|
||||
hoverSet.push(xLabel)
|
||||
yLabels = []
|
||||
for i in [0..@series.length-1]
|
||||
yLabel = @r.text(0, @options.hoverFontSize * 1.5 * (i + 1.5) - hoverHeight / 2, '')
|
||||
.attr('fill', @options.lineColors[i])
|
||||
.attr('font-size', @options.hoverFontSize)
|
||||
yLabels.push(yLabel)
|
||||
hoverSet.push(yLabel)
|
||||
updateHover = (index) =>
|
||||
hoverSet.show()
|
||||
xLabel.attr('text', @columnLabels[index])
|
||||
for i in [0..@series.length-1]
|
||||
yLabels[i].attr('text', "#{@seriesLabels[i]}: #{@commas(@series[i][index])}")
|
||||
# recalculate hover box width
|
||||
maxLabelWidth = Math.max.apply null, $.map yLabels, (l) ->
|
||||
l.getBBox().width
|
||||
maxLabelWidth = Math.max maxLabelWidth, xLabel.getBBox().width
|
||||
hover.attr 'width', maxLabelWidth + @options.hoverPaddingX * 2
|
||||
hover.attr 'x', -@options.hoverPaddingX - maxLabelWidth / 2
|
||||
# move to y pos
|
||||
yloc = Math.min.apply null, $.map @series, (s) =>
|
||||
transY s[index]
|
||||
if yloc > hoverHeight + @options.hoverPaddingY * 2 + @options.hoverMargin + @options.marginTop
|
||||
yloc = yloc - hoverHeight / 2 - @options.hoverPaddingY - @options.hoverMargin
|
||||
else
|
||||
yloc = yloc + hoverHeight / 2 + @options.hoverPaddingY + @options.hoverMargin
|
||||
yloc = Math.max @options.marginTop + hoverHeight / 2 + @options.hoverPaddingY, yloc
|
||||
yloc = Math.min @options.marginTop + height - hoverHeight / 2 - @options.hoverPaddingY, yloc
|
||||
xloc = Math.min left + width - maxLabelWidth / 2 - @options.hoverPaddingX, columns[index]
|
||||
xloc = Math.max left + maxLabelWidth / 2 + @options.hoverPaddingX, xloc
|
||||
hoverSet.attr 'transform', "t#{xloc},#{yloc}"
|
||||
hideHover = ->
|
||||
hoverSet.hide()
|
||||
|
||||
# column hilight
|
||||
hoverMargins = $.map columns.slice(1), (x, i) -> (x + columns[i]) / 2
|
||||
prevHilight = null
|
||||
pointGrow = Raphael.animation r: @options.pointSize + 3, 25, 'linear'
|
||||
pointShrink = Raphael.animation r: @options.pointSize, 25, 'linear'
|
||||
hilight = (index) =>
|
||||
if prevHilight isnt null and prevHilight isnt index
|
||||
for i in [0..seriesPoints.length-1]
|
||||
seriesPoints[i][prevHilight].animate pointShrink
|
||||
if index isnt null and prevHilight isnt index
|
||||
for i in [0..seriesPoints.length-1]
|
||||
seriesPoints[i][index].animate pointGrow
|
||||
updateHover index
|
||||
prevHilight = index
|
||||
if index is null
|
||||
hideHover()
|
||||
updateHilight = (x) =>
|
||||
x -= @el.offset().left
|
||||
for i in [hoverMargins.length..1]
|
||||
if hoverMargins[i - 1] > x
|
||||
break
|
||||
hilight i
|
||||
@el.mousemove (evt) =>
|
||||
updateHilight evt.pageX
|
||||
touchHandler = (evt) =>
|
||||
touch = evt.originalEvent.touches[0] or evt.originalEvent.changedTouches[0]
|
||||
updateHilight touch.pageX
|
||||
return touch
|
||||
@el.bind 'touchstart', touchHandler
|
||||
@el.bind 'touchmove', touchHandler
|
||||
@el.bind 'touchend', touchHandler
|
||||
hilight 0
|
||||
|
||||
# create a path for a data series
|
||||
#
|
||||
createPath: (coords, top, left, bottom, right) ->
|
||||
path = ""
|
||||
grads = @gradients coords
|
||||
for i in [0..coords.length-1]
|
||||
c = coords[i]
|
||||
if i is 0
|
||||
path += "M#{c.x},#{c.y}"
|
||||
else
|
||||
g = grads[i]
|
||||
lc = coords[i - 1]
|
||||
lg = grads[i - 1]
|
||||
ix = (c.x - lc.x) / 4
|
||||
x1 = lc.x + ix
|
||||
y1 = Math.min(bottom, lc.y + ix * lg)
|
||||
x2 = c.x - ix
|
||||
y2 = Math.min(bottom, c.y - ix * g)
|
||||
path += "C#{x1},#{y1},#{x2},#{y2},#{c.x},#{c.y}"
|
||||
return path
|
||||
|
||||
# calculate a gradient at each point for a series of points
|
||||
#
|
||||
gradients: (coords) ->
|
||||
$.map coords, (c, i) ->
|
||||
if i is 0
|
||||
(coords[1].y - c.y) / (coords[1].x - c.x)
|
||||
else if i is (coords.length - 1)
|
||||
(c.y - coords[i - 1].y) / (c.x - coords[i - 1].x)
|
||||
else
|
||||
(coords[i + 1].y - coords[i - 1].y) / (coords[i + 1].x - coords[i - 1].x)
|
||||
|
||||
measureText: (text, fontSize = 12) ->
|
||||
tt = @r.text(100, 100, text).attr('font-size', fontSize)
|
||||
ret = tt.getBBox()
|
||||
tt.remove()
|
||||
return ret
|
||||
|
||||
parseYear: (date) ->
|
||||
s = date.toString()
|
||||
m = s.match /^(\d+) Q(\d)$/
|
||||
n = s.match /^(\d+)-(\d+)$/
|
||||
o = s.match /^(\d+)-(\d+)-(\d+)$/
|
||||
if m
|
||||
parseInt(m[1], 10) + (parseInt(m[2], 10) * 3 - 1) / 12
|
||||
else if n
|
||||
parseInt(n[1], 10) + (parseInt(n[2], 10) - 1) / 12
|
||||
else if o
|
||||
# parse to a timestamp
|
||||
year = parseInt(o[1], 10);
|
||||
month = parseInt(o[2], 10);
|
||||
day = parseInt(o[3], 10);
|
||||
timestamp = new Date(year, month - 1, day).getTime();
|
||||
# get timestamps for the beginning and end of the year
|
||||
y1 = new Date(year, 0, 1).getTime();
|
||||
y2 = new Date(year+1, 0, 1).getTime();
|
||||
# calculate a decimal-year value
|
||||
year + (timestamp - y1) / (y2 - y1);
|
||||
else
|
||||
parseInt(d, 10)
|
||||
|
||||
# make long numbers prettier by inserting commas
|
||||
# eg: commas(1234567) -> '1,234,567'
|
||||
#
|
||||
commas: (num) ->
|
||||
Math.max(0, num).toFixed(0).replace(/(?=(?:\d{3})+$)(?!^)/g, ',')
|
||||
|
||||
# vim: set et ts=2 sw=2 sts=2
|
592
morris.js
592
morris.js
|
@ -1,306 +1,318 @@
|
|||
(function() {
|
||||
|
||||
/*global jQuery: false, Raphael: false */
|
||||
window.Morris = {};
|
||||
|
||||
function parse_year(date) {
|
||||
var m = date.toString().match(/^(\d+) Q(\d)$/);
|
||||
var n = date.toString().match(/^(\d+)-(\d+)$/);
|
||||
var o = date.toString().match(/^(\d+)-(\d+)-(\d+)$/)
|
||||
if (m) {
|
||||
return parseInt(m[1], 10) + (parseInt(m[2], 10) * 3 - 1) / 12;
|
||||
}
|
||||
else if (n) {
|
||||
return parseInt(n[1], 10) + (parseInt(n[2], 10) - 1) / 12;
|
||||
}
|
||||
else if (o) {
|
||||
// parse to a timestamp
|
||||
var year = parseInt(o[1], 10);
|
||||
var month = parseInt(o[2], 10);
|
||||
var day = parseInt(o[3], 10);
|
||||
var timestamp = new Date(year, month - 1, day).getTime();
|
||||
// get timestamps for the beginning and end of the year
|
||||
var y1 = new Date(year, 0, 1).getTime();
|
||||
var y2 = new Date(year+1, 0, 1).getTime();
|
||||
// calculate a decimal-year value
|
||||
return year + (timestamp - y1) / (y2 - y1);
|
||||
}
|
||||
else {
|
||||
return parseInt(date, 10);
|
||||
}
|
||||
}
|
||||
window.Morris.Line = (function() {
|
||||
|
||||
function setup_graph(config) {
|
||||
/*jshint loopfunc: true */
|
||||
var data = config.data;
|
||||
if (data.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.addClass('graph-initialised');
|
||||
var xlabels = $.map(data, function (d) { return d[config.xkey]; });
|
||||
var series = config.ykeys;
|
||||
var labels = config.labels;
|
||||
if (!data || !data.length) {
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < series.length; i++) {
|
||||
series[i] = $.map(data, function (d) { return d[series[i]]; });
|
||||
}
|
||||
var xvals = $.map(xlabels, function (x) { return parse_year(x); });
|
||||
|
||||
var xmin = Math.min.apply(null, xvals);
|
||||
var xmax = Math.max.apply(null, xvals);
|
||||
if (xmin === xmax) {
|
||||
xmin -= 1;
|
||||
xmax += 1;
|
||||
}
|
||||
var ymax = Math.max(20, Math.max.apply(null,
|
||||
$.map(series, function (s) { return Math.max.apply(null, s); })));
|
||||
var r = new Raphael(this[0]);
|
||||
var margin_top = 25, margin_bottom = 30, margin_right = 25;
|
||||
var tt = r.text(100, 100, ymax).attr('font-size', 12);
|
||||
var margin_left = 25 + tt.getBBox().width;
|
||||
tt.remove();
|
||||
var h = this.height() - margin_top - margin_bottom;
|
||||
var w = this.width() - margin_left - margin_right;
|
||||
var dx = w / (xmax - xmin);
|
||||
var dy = h / ymax;
|
||||
|
||||
function trans_x(x) {
|
||||
if (xvals.length === 1) {
|
||||
return margin_left + w / 2;
|
||||
function Line(id, options) {
|
||||
this.el = $(document.getElementById(id));
|
||||
this.options = $.extend(this.defaults, options);
|
||||
if (this.options.data === void 0 || this.options.data.length === 0) return;
|
||||
this.el.addClass('graph-initialised');
|
||||
this.precalc();
|
||||
this.redraw();
|
||||
}
|
||||
else {
|
||||
return margin_left + (x - xmin) * dx;
|
||||
}
|
||||
}
|
||||
function trans_y(y) {
|
||||
return margin_top + h - y * dy;
|
||||
}
|
||||
|
||||
// draw horizontal lines
|
||||
var num_lines = 5;
|
||||
var line_interval = h / (num_lines - 1);
|
||||
for (i = 0; i < num_lines; i++) {
|
||||
var y = margin_top + i * line_interval;
|
||||
r.text(margin_left - 12, y, Math.floor((num_lines - 1 - i) * ymax / (num_lines - 1)))
|
||||
.attr('font-size', 12)
|
||||
.attr('fill', '#888')
|
||||
.attr('text-anchor', 'end');
|
||||
r.path("M" + (margin_left) + "," + y + "L" + (margin_left + w) + "," + y)
|
||||
.attr('stroke', '#aaa')
|
||||
.attr('stroke-width', 0.5);
|
||||
}
|
||||
|
||||
// calculate the columns
|
||||
var cols = $.map(xvals, trans_x);
|
||||
var hover_margins = $.map(cols.slice(1),
|
||||
function (x, i) { return (x + cols[i]) / 2; });
|
||||
Line.prototype.defaults = {
|
||||
lineWidth: 3,
|
||||
pointSize: 4,
|
||||
lineColors: ['#0b62a4', '#7A92A3', '#4da74d', '#afd8f8', '#edc240', '#cb4b4b', '#9440ed'],
|
||||
marginTop: 25,
|
||||
marginRight: 25,
|
||||
marginBottom: 30,
|
||||
marginLeft: 25,
|
||||
numLines: 5,
|
||||
gridLineColor: '#aaa',
|
||||
gridTextColor: '#888',
|
||||
gridTextSize: 12,
|
||||
gridStrokeWidth: 0.5,
|
||||
hoverPaddingX: 10,
|
||||
hoverPaddingY: 5,
|
||||
hoverMargin: 10,
|
||||
hoverFillColor: '#fff',
|
||||
hoverBorderColor: '#ccc',
|
||||
hoverBorderWidth: 2,
|
||||
hoverOpacity: 0.95,
|
||||
hoverLabelColor: '#444',
|
||||
hoverFontSize: 12
|
||||
};
|
||||
|
||||
var last_label = null;
|
||||
var ylabel_margin = 50;
|
||||
for (i = Math.ceil(xmin); i <= Math.floor(xmax); i++) {
|
||||
var label = r.text(trans_x(i), margin_top + h + margin_bottom / 2, i)
|
||||
.attr('font-size', 12)
|
||||
.attr('fill', '#888');
|
||||
if (last_label !== null) {
|
||||
var bb1 = last_label.getBBox();
|
||||
var bb2 = label.getBBox();
|
||||
if (bb1.x + bb1.width + ylabel_margin > bb2.x) {
|
||||
label.remove();
|
||||
Line.prototype.precalc = function() {
|
||||
var all_y_vals, ykey, _i, _len, _ref,
|
||||
_this = this;
|
||||
this.columnLabels = $.map(this.options.data, function(d) {
|
||||
return d[_this.options.xkey];
|
||||
});
|
||||
this.seriesLabels = this.options.labels;
|
||||
this.series = [];
|
||||
_ref = this.options.ykeys;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
ykey = _ref[_i];
|
||||
this.series.push($.map(this.options.data, function(d) {
|
||||
return d[ykey];
|
||||
}));
|
||||
}
|
||||
else {
|
||||
last_label = label;
|
||||
this.xvals = $.map(this.columnLabels, function(x) {
|
||||
return _this.parseYear(x);
|
||||
});
|
||||
this.xmin = Math.min.apply(null, this.xvals);
|
||||
this.xmax = Math.max.apply(null, this.xvals);
|
||||
if (this.xmin === this.xmax) {
|
||||
this.xmin -= 1;
|
||||
this.xmax += 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
last_label = label;
|
||||
}
|
||||
}
|
||||
all_y_vals = $.map(this.series, function(x) {
|
||||
return Math.max.apply(null, x);
|
||||
});
|
||||
return this.ymax = Math.max(20, Math.max.apply(null, all_y_vals));
|
||||
};
|
||||
|
||||
// draw the series
|
||||
var series_points = [];
|
||||
for (var s = (series.length - 1); s >= 0; s--) {
|
||||
var path = '';
|
||||
var lc = null;
|
||||
var lg = null;
|
||||
// translate the coordinates into screen positions
|
||||
var coords = $.map(series[s],
|
||||
function (v, idx) { return {x: cols[idx], y: trans_y(v)}; });
|
||||
if (coords.length > 1) {
|
||||
// calculate the gradients
|
||||
var grads = $.map(coords, function (c, i) {
|
||||
Line.prototype.redraw = function() {
|
||||
var c, circle, columns, coords, dx, dy, height, hideHover, hilight, hover, hoverHeight, hoverMargins, hoverSet, i, label, labelBox, left, lineInterval, path, pointGrow, pointShrink, prevHilight, prevLabelMargin, s, seriesCoords, seriesPoints, touchHandler, transX, transY, updateHilight, updateHover, v, width, x, xLabel, xLabelMargin, y, yLabel, yLabels, _i, _j, _len, _len2, _ref, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8,
|
||||
_this = this;
|
||||
this.el.empty();
|
||||
this.r = new Raphael(this.el[0]);
|
||||
left = this.measureText(this.ymax, this.options.gridTextSize).width + this.options.marginLeft;
|
||||
width = this.el.width() - left - this.options.marginRight;
|
||||
height = this.el.height() - this.options.marginTop - this.options.marginBottom;
|
||||
dx = width / (this.xmax - this.xmin);
|
||||
dy = height / this.ymax;
|
||||
transX = function(x) {
|
||||
if (_this.xvals.length === 1) {
|
||||
return left + width / 2;
|
||||
} else {
|
||||
return left + (x - _this.xmin) * dx;
|
||||
}
|
||||
};
|
||||
transY = function(y) {
|
||||
return _this.options.marginTop + height - y * dy;
|
||||
};
|
||||
lineInterval = height / (this.options.numLines - 1);
|
||||
for (i = 0, _ref = this.options.numLines - 1; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) {
|
||||
y = this.options.marginTop + i * lineInterval;
|
||||
v = Math.round((this.options.numLines - 1 - i) * this.ymax / (this.options.numLines - 1));
|
||||
this.r.text(left - this.options.marginLeft / 2, y, v).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor).attr('text-anchor', 'end');
|
||||
this.r.path("M" + left + "," + y + 'H' + (left + width)).attr('stroke', this.options.gridLineColor).attr('stroke-width', this.options.gridStrokeWidth);
|
||||
}
|
||||
prevLabelMargin = null;
|
||||
xLabelMargin = 50;
|
||||
for (i = _ref2 = Math.ceil(this.xmin), _ref3 = Math.floor(this.xmax); _ref2 <= _ref3 ? i <= _ref3 : i >= _ref3; _ref2 <= _ref3 ? i++ : i--) {
|
||||
label = this.r.text(transX(i), this.options.marginTop + height + this.options.marginBottom / 2, i).attr('font-size', this.options.gridTextSize).attr('fill', this.options.gridTextColor);
|
||||
labelBox = label.getBBox();
|
||||
if (prevLabelMargin === null || prevLabelMargin <= labelBox.x) {
|
||||
prevLabelMargin = labelBox.x + labelBox.width + xLabelMargin;
|
||||
} else {
|
||||
label.remove();
|
||||
}
|
||||
}
|
||||
columns = (function() {
|
||||
var _i, _len, _ref4, _results;
|
||||
_ref4 = this.xvals;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
|
||||
x = _ref4[_i];
|
||||
_results.push(transX(x));
|
||||
}
|
||||
return _results;
|
||||
}).call(this);
|
||||
seriesCoords = [];
|
||||
_ref4 = this.series;
|
||||
for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
|
||||
s = _ref4[_i];
|
||||
seriesCoords.push($.map(s, function(y, i) {
|
||||
return {
|
||||
x: columns[i],
|
||||
y: transY(y)
|
||||
};
|
||||
}));
|
||||
}
|
||||
for (i = _ref5 = seriesCoords.length - 1; _ref5 <= 0 ? i <= 0 : i >= 0; _ref5 <= 0 ? i++ : i--) {
|
||||
coords = seriesCoords[i];
|
||||
if (coords.length > 1) {
|
||||
path = this.createPath(coords, this.options.marginTop, left, this.options.marginTop + height, left + width);
|
||||
this.r.path(path).attr('stroke', this.options.lineColors[i]).attr('stroke-width', this.options.lineWidth);
|
||||
}
|
||||
}
|
||||
seriesPoints = (function() {
|
||||
var _ref6, _results;
|
||||
_results = [];
|
||||
for (i = 0, _ref6 = seriesCoords.length - 1; 0 <= _ref6 ? i <= _ref6 : i >= _ref6; 0 <= _ref6 ? i++ : i--) {
|
||||
_results.push([]);
|
||||
}
|
||||
return _results;
|
||||
})();
|
||||
for (i = _ref6 = seriesCoords.length - 1; _ref6 <= 0 ? i <= 0 : i >= 0; _ref6 <= 0 ? i++ : i--) {
|
||||
_ref7 = seriesCoords[i];
|
||||
for (_j = 0, _len2 = _ref7.length; _j < _len2; _j++) {
|
||||
c = _ref7[_j];
|
||||
circle = this.r.circle(c.x, c.y, this.options.pointSize).attr('fill', this.options.lineColors[i]).attr('stroke-width', 1).attr('stroke', '#ffffff');
|
||||
seriesPoints[i].push(circle);
|
||||
}
|
||||
}
|
||||
hoverHeight = this.options.hoverFontSize * 1.5 * (this.series.length + 1);
|
||||
hover = this.r.rect(-10, -hoverHeight / 2 - this.options.hoverPaddingY, 20, hoverHeight + this.options.hoverPaddingY * 2, 10).attr('fill', this.options.hoverFillColor).attr('stroke', this.options.hoverBorderColor).attr('stroke-width', this.options.hoverBorderWidth).attr('opacity', this.options.hoverOpacity);
|
||||
xLabel = this.r.text(0, (this.options.hoverFontSize * 0.75) - hoverHeight / 2, '').attr('fill', this.options.hoverLabelColor).attr('font-weight', 'bold').attr('font-size', this.options.hoverFontSize);
|
||||
hoverSet = this.r.set();
|
||||
hoverSet.push(hover);
|
||||
hoverSet.push(xLabel);
|
||||
yLabels = [];
|
||||
for (i = 0, _ref8 = this.series.length - 1; 0 <= _ref8 ? i <= _ref8 : i >= _ref8; 0 <= _ref8 ? i++ : i--) {
|
||||
yLabel = this.r.text(0, this.options.hoverFontSize * 1.5 * (i + 1.5) - hoverHeight / 2, '').attr('fill', this.options.lineColors[i]).attr('font-size', this.options.hoverFontSize);
|
||||
yLabels.push(yLabel);
|
||||
hoverSet.push(yLabel);
|
||||
}
|
||||
updateHover = function(index) {
|
||||
var i, maxLabelWidth, xloc, yloc, _ref9;
|
||||
hoverSet.show();
|
||||
xLabel.attr('text', _this.columnLabels[index]);
|
||||
for (i = 0, _ref9 = _this.series.length - 1; 0 <= _ref9 ? i <= _ref9 : i >= _ref9; 0 <= _ref9 ? i++ : i--) {
|
||||
yLabels[i].attr('text', "" + _this.seriesLabels[i] + ": " + (_this.commas(_this.series[i][index])));
|
||||
}
|
||||
maxLabelWidth = Math.max.apply(null, $.map(yLabels, function(l) {
|
||||
return l.getBBox().width;
|
||||
}));
|
||||
maxLabelWidth = Math.max(maxLabelWidth, xLabel.getBBox().width);
|
||||
hover.attr('width', maxLabelWidth + _this.options.hoverPaddingX * 2);
|
||||
hover.attr('x', -_this.options.hoverPaddingX - maxLabelWidth / 2);
|
||||
yloc = Math.min.apply(null, $.map(_this.series, function(s) {
|
||||
return transY(s[index]);
|
||||
}));
|
||||
if (yloc > hoverHeight + _this.options.hoverPaddingY * 2 + _this.options.hoverMargin + _this.options.marginTop) {
|
||||
yloc = yloc - hoverHeight / 2 - _this.options.hoverPaddingY - _this.options.hoverMargin;
|
||||
} else {
|
||||
yloc = yloc + hoverHeight / 2 + _this.options.hoverPaddingY + _this.options.hoverMargin;
|
||||
}
|
||||
yloc = Math.max(_this.options.marginTop + hoverHeight / 2 + _this.options.hoverPaddingY, yloc);
|
||||
yloc = Math.min(_this.options.marginTop + height - hoverHeight / 2 - _this.options.hoverPaddingY, yloc);
|
||||
xloc = Math.min(left + width - maxLabelWidth / 2 - _this.options.hoverPaddingX, columns[index]);
|
||||
xloc = Math.max(left + maxLabelWidth / 2 + _this.options.hoverPaddingX, xloc);
|
||||
return hoverSet.attr('transform', "t" + xloc + "," + yloc);
|
||||
};
|
||||
hideHover = function() {
|
||||
return hoverSet.hide();
|
||||
};
|
||||
hoverMargins = $.map(columns.slice(1), function(x, i) {
|
||||
return (x + columns[i]) / 2;
|
||||
});
|
||||
prevHilight = null;
|
||||
pointGrow = Raphael.animation({
|
||||
r: this.options.pointSize + 3
|
||||
}, 25, 'linear');
|
||||
pointShrink = Raphael.animation({
|
||||
r: this.options.pointSize
|
||||
}, 25, 'linear');
|
||||
hilight = function(index) {
|
||||
var i, _ref10, _ref9;
|
||||
if (prevHilight !== null && prevHilight !== index) {
|
||||
for (i = 0, _ref9 = seriesPoints.length - 1; 0 <= _ref9 ? i <= _ref9 : i >= _ref9; 0 <= _ref9 ? i++ : i--) {
|
||||
seriesPoints[i][prevHilight].animate(pointShrink);
|
||||
}
|
||||
}
|
||||
if (index !== null && prevHilight !== index) {
|
||||
for (i = 0, _ref10 = seriesPoints.length - 1; 0 <= _ref10 ? i <= _ref10 : i >= _ref10; 0 <= _ref10 ? i++ : i--) {
|
||||
seriesPoints[i][index].animate(pointGrow);
|
||||
}
|
||||
updateHover(index);
|
||||
}
|
||||
prevHilight = index;
|
||||
if (index === null) return hideHover();
|
||||
};
|
||||
updateHilight = function(x) {
|
||||
var i, _ref9;
|
||||
x -= _this.el.offset().left;
|
||||
for (i = _ref9 = hoverMargins.length; _ref9 <= 1 ? i <= 1 : i >= 1; _ref9 <= 1 ? i++ : i--) {
|
||||
if (hoverMargins[i - 1] > x) break;
|
||||
}
|
||||
return hilight(i);
|
||||
};
|
||||
this.el.mousemove(function(evt) {
|
||||
return updateHilight(evt.pageX);
|
||||
});
|
||||
touchHandler = function(evt) {
|
||||
var touch;
|
||||
touch = evt.originalEvent.touches[0] || evt.originalEvent.changedTouches[0];
|
||||
updateHilight(touch.pageX);
|
||||
return touch;
|
||||
};
|
||||
this.el.bind('touchstart', touchHandler);
|
||||
this.el.bind('touchmove', touchHandler);
|
||||
this.el.bind('touchend', touchHandler);
|
||||
return hilight(0);
|
||||
};
|
||||
|
||||
Line.prototype.createPath = function(coords, top, left, bottom, right) {
|
||||
var c, g, grads, i, ix, lc, lg, path, x1, x2, y1, y2, _ref;
|
||||
path = "";
|
||||
grads = this.gradients(coords);
|
||||
for (i = 0, _ref = coords.length - 1; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) {
|
||||
c = coords[i];
|
||||
if (i === 0) {
|
||||
path += "M" + c.x + "," + c.y;
|
||||
} else {
|
||||
g = grads[i];
|
||||
lc = coords[i - 1];
|
||||
lg = grads[i - 1];
|
||||
ix = (c.x - lc.x) / 4;
|
||||
x1 = lc.x + ix;
|
||||
y1 = Math.min(bottom, lc.y + ix * lg);
|
||||
x2 = c.x - ix;
|
||||
y2 = Math.min(bottom, c.y - ix * g);
|
||||
path += "C" + x1 + "," + y1 + "," + x2 + "," + y2 + "," + c.x + "," + c.y;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
Line.prototype.gradients = function(coords) {
|
||||
return $.map(coords, function(c, i) {
|
||||
if (i === 0) {
|
||||
return (coords[1].y - c.y) / (coords[1].x - c.x);
|
||||
}
|
||||
else if (i === xvals.length - 1) {
|
||||
} else if (i === (coords.length - 1)) {
|
||||
return (c.y - coords[i - 1].y) / (c.x - coords[i - 1].x);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return (coords[i + 1].y - coords[i - 1].y) / (coords[i + 1].x - coords[i - 1].x);
|
||||
}
|
||||
});
|
||||
for (i = 0; i < coords.length; i++) {
|
||||
var c = coords[i];
|
||||
var g = grads[i];
|
||||
if (i === 0) {
|
||||
path += "M" + ([c.x, c.y].join(','));
|
||||
}
|
||||
else {
|
||||
var ix = (c.x - lc.x) / 4;
|
||||
path += "C" + ([lc.x + ix,
|
||||
Math.min(margin_top + h, lc.y + ix * lg),
|
||||
c.x - ix,
|
||||
Math.min(margin_top + h, c.y - ix * g),
|
||||
c.x, c.y].join(','));
|
||||
}
|
||||
lc = c;
|
||||
lg = g;
|
||||
}
|
||||
r.path(path)
|
||||
.attr('stroke', config.line_colors[s])
|
||||
.attr('stroke-width', config.line_width);
|
||||
// draw the points
|
||||
}
|
||||
series_points.push([]);
|
||||
for (i = 0; i < series[s].length; i++) {
|
||||
var c1 = {x: cols[i], y: trans_y(series[s][i])};
|
||||
var circle = r.circle(c1.x, c1.y, config.point_size)
|
||||
.attr('fill', config.line_colors[s])
|
||||
.attr('stroke-width', 1)
|
||||
.attr('stroke', '#ffffff');
|
||||
series_points[series_points.length - 1].push(circle);
|
||||
}
|
||||
}
|
||||
|
||||
// hover labels
|
||||
var label_height = 12;
|
||||
var label_padding_x = 10;
|
||||
var label_padding_y = 5;
|
||||
var label_margin = 10;
|
||||
var yvar_labels = [];
|
||||
var label_float_height = (label_height * 1.5) * (series.length + 1);
|
||||
var label_float = r.rect(-10, -label_float_height / 2 - label_padding_y, 20, label_float_height + label_padding_y * 2, 10)
|
||||
.attr('fill', '#fff')
|
||||
.attr('stroke', '#ccc')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('opacity', 0.95);
|
||||
var xvar_label = r.text(0, (label_height * 0.75) - (label_float_height / 2), '')
|
||||
.attr('fill', '#444')
|
||||
.attr('font-weight', 'bold')
|
||||
.attr('font-size', label_height);
|
||||
var label_set = r.set();
|
||||
label_set.push(label_float);
|
||||
label_set.push(xvar_label);
|
||||
for (i = 0; i < series.length; i++) {
|
||||
var yl = r.text(0, (label_height * 1.5 * (i + 1.5)) - (label_float_height / 2), '')
|
||||
.attr('fill', config.line_colors[i])
|
||||
.attr('font-size', label_height);
|
||||
yvar_labels.push(yl);
|
||||
label_set.push(yl);
|
||||
}
|
||||
function commas(v) {
|
||||
v = v.toString();
|
||||
var r = "";
|
||||
while (v.length > 3) {
|
||||
r = "," + v.substr(v.length - 3) + r;
|
||||
v = v.substr(0, v.length - 3);
|
||||
}
|
||||
r = v + r;
|
||||
return r;
|
||||
}
|
||||
function update_float(index) {
|
||||
label_set.show();
|
||||
xvar_label.attr('text', xlabels[index]);
|
||||
for (var i = 0; i < series.length; i++) {
|
||||
yvar_labels[i].attr('text', labels[i] + ': ' + commas(series[i][index]));
|
||||
}
|
||||
// calculate bbox width
|
||||
var bbw = Math.max(xvar_label.getBBox().width,
|
||||
Math.max.apply(null, $.map(yvar_labels, function (l) { return l.getBBox().width; })));
|
||||
label_float.attr('width', bbw + label_padding_x * 2);
|
||||
label_float.attr('x', -label_padding_x - bbw / 2);
|
||||
// determine y-pos
|
||||
var yloc = Math.min.apply(null, $.map(series, function (s) { return trans_y(s[index]); }));
|
||||
if (yloc > label_float_height + label_padding_y * 2 + label_margin + margin_top) {
|
||||
yloc = yloc - label_float_height / 2 - label_padding_y - label_margin;
|
||||
}
|
||||
else {
|
||||
yloc = yloc + label_float_height / 2 + label_padding_y + label_margin;
|
||||
}
|
||||
yloc = Math.max(margin_top + label_float_height / 2 + label_padding_y, yloc);
|
||||
yloc = Math.min(margin_top + h - label_float_height / 2 - label_padding_y, yloc);
|
||||
var xloc = Math.min(margin_left + w - bbw / 2 - label_padding_y, cols[index]);
|
||||
xloc = Math.max(margin_left + bbw / 2 + label_padding_x, xloc);
|
||||
label_set.attr('transform', 't' + xloc + ',' + yloc);
|
||||
}
|
||||
function hide_float() {
|
||||
label_set.hide();
|
||||
}
|
||||
|
||||
// column hilighting
|
||||
var self = this;
|
||||
var prev_hilight = null;
|
||||
var point_grow = Raphael.animation({r: config.point_size + 3}, 25, "linear");
|
||||
var point_shrink = Raphael.animation({r: config.point_size}, 25, "linear");
|
||||
function highlight(index) {
|
||||
var j;
|
||||
if (prev_hilight !== null && prev_hilight !== index) {
|
||||
for (j = 0; j < series_points.length; j++) {
|
||||
series_points[j][prev_hilight].animate(point_shrink);
|
||||
}
|
||||
}
|
||||
if (index !== null && prev_hilight !== index) {
|
||||
for (j = 0; j < series_points.length; j++) {
|
||||
series_points[j][index].animate(point_grow);
|
||||
}
|
||||
update_float(index);
|
||||
}
|
||||
prev_hilight = index;
|
||||
if (index === null) {
|
||||
hide_float();
|
||||
}
|
||||
}
|
||||
function update_hilight(x_coord) {
|
||||
var x = x_coord - self.offset().left;
|
||||
for (var i = hover_margins.length; i > 0; i--) {
|
||||
if (hover_margins[i - 1] > x) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
highlight(i);
|
||||
}
|
||||
this.mousemove(function (evt) {
|
||||
update_hilight(evt.pageX);
|
||||
});
|
||||
function touchhandler(evt) {
|
||||
var touch = evt.originalEvent.touches[0] ||
|
||||
evt.originalEvent.changedTouches[0];
|
||||
update_hilight(touch.pageX);
|
||||
return touch;
|
||||
}
|
||||
this.bind('touchstart', touchhandler);
|
||||
this.bind('touchmove', touchhandler);
|
||||
this.bind('touchend', touchhandler);
|
||||
highlight(0);
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.hml = function (options) {
|
||||
var config = {
|
||||
line_width: 3,
|
||||
point_size: 4,
|
||||
line_colors: [
|
||||
'#0b62a4',
|
||||
'#7A92A3',
|
||||
'#4da74d',
|
||||
'#afd8f8',
|
||||
'#edc240',
|
||||
'#cb4b4b',
|
||||
'#9440ed'
|
||||
]
|
||||
};
|
||||
if (options) {
|
||||
$.extend(config, options);
|
||||
}
|
||||
return this.each(function () {
|
||||
setup_graph.call($(this), config);
|
||||
});
|
||||
};
|
||||
Line.prototype.measureText = function(text, fontSize) {
|
||||
var ret, tt;
|
||||
if (fontSize == null) fontSize = 12;
|
||||
tt = this.r.text(100, 100, text).attr('font-size', fontSize);
|
||||
ret = tt.getBBox();
|
||||
tt.remove();
|
||||
return ret;
|
||||
};
|
||||
|
||||
Line.prototype.parseYear = function(date) {
|
||||
var day, m, month, n, o, s, timestamp, y1, y2, year;
|
||||
s = date.toString();
|
||||
m = s.match(/^(\d+) Q(\d)$/);
|
||||
n = s.match(/^(\d+)-(\d+)$/);
|
||||
o = s.match(/^(\d+)-(\d+)-(\d+)$/);
|
||||
if (m) {
|
||||
return parseInt(m[1], 10) + (parseInt(m[2], 10) * 3 - 1) / 12;
|
||||
} else if (n) {
|
||||
return parseInt(n[1], 10) + (parseInt(n[2], 10) - 1) / 12;
|
||||
} else if (o) {
|
||||
year = parseInt(o[1], 10);
|
||||
month = parseInt(o[2], 10);
|
||||
day = parseInt(o[3], 10);
|
||||
timestamp = new Date(year, month - 1, day).getTime();
|
||||
y1 = new Date(year, 0, 1).getTime();
|
||||
y2 = new Date(year + 1, 0, 1).getTime();
|
||||
return year + (timestamp - y1) / (y2 - y1);
|
||||
} else {
|
||||
return parseInt(d, 10);
|
||||
}
|
||||
};
|
||||
|
||||
Line.prototype.commas = function(num) {
|
||||
return Math.max(0, num).toFixed(0).replace(/(?=(?:\d{3})+$)(?!^)/g, ',');
|
||||
};
|
||||
|
||||
return Line;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
||||
|
|
Loading…
Reference in a new issue