app/assets/javascripts/widgets/indicators/line/LineChart.js
define(
[
'jquery',
'd3',
'underscore',
'mps',
'widgets/indicators/line/line_chart_context',
'widgets/indicators/line/line_chart_interactionHandler',
'text!widgets/templates/indicators/line/linechart-tooltip.handlebars'
],
function(
$,
d3,
_,
mps,
LineChartContext,
LineChartInteractionHandler,
tooltipTpl
) {
var LineChart = function(options) {
this.svg;
this.options = options;
this.parent = options.parent;
this.data = options.data;
this.unit = options.unit;
this.unitname = options.unitname;
this.range = options.range;
this.templateTooltip = Handlebars.compile(tooltipTpl);
this.color = ['#5B80A0', '#bebcc2', '#E98300'];
this.sizing = options.sizing;
this.innerPadding = options.innerPadding;
this.namespace = new Date().valueOf().toString();
this.parentWidth = $(this.options.el).outerWidth();
this.parentHeight = $(this.options.el).outerHeight();
(this.width = this.parentWidth - this.sizing.left - this.sizing.right),
(this.height =
this.parentHeight - this.sizing.top - this.sizing.bottom);
if (!!this.parentWidth && !!this.parentHeight) {
this._createEl();
this._createDefs();
this._createScales();
}
this.setListeners();
// $(window).resize(_.debounce(this.resize.bind(this), 100))
$(window).on(
'resize.namespace' + this.namespace,
_.debounce(this.resize.bind(this), 100)
);
};
LineChart.prototype.offResize = function() {
$(window).off('resize.namespace' + this.namespace);
};
LineChart.prototype.resize = function() {
this.offResize();
$(this.options.el)
.find('svg')
.remove();
new LineChart(this.options).render();
};
LineChart.prototype._createEl = function() {
this.svg = d3
.select(this.options.el)
.append('svg')
.attr('class', 'lineChart')
.attr('width', this.parentWidth)
.attr('height', this.parentHeight);
};
LineChart.prototype._createScales = function() {
var self = this;
this.xKey = this.options.keys.x;
this.yKey = this.options.keys.y;
this.x = d3.time
.scale()
.range([
this.options.innerPadding.left,
this.width - this.options.innerPadding.right
]);
this.x.domain(
d3.extent(
this.data.map(function(d) {
return d[self.xKey];
})
)
);
// this.y = d3.scale.linear().range([this.height,this.options.innerPadding.top]);
this.y = d3.scale
.linear()
.range([
this.height - this.options.innerPadding.bottom,
this.options.innerPadding.top
]);
this.y.domain(this.range);
this.line = d3.svg
.line()
.interpolate('linear')
.x(function(d) {
return self.x(d[self.xKey]);
})
.y(function(d) {
return self.y(d[self.yKey]);
});
};
LineChart.prototype._createDefs = function() {
this.svg
.append('defs')
.append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', this.width)
.attr('height', this.height);
};
LineChart.prototype._drawAxes = function(group) {
var self = this;
var tickFormatY = this.unit != 'percentage' ? 's' : '.2f';
this.xAxis = d3.svg
.axis()
.scale(self.x)
.ticks(d3.time.year, 1)
.tickSize(6, 6)
.orient('bottom')
.tickFormat(d3.time.format('%Y'));
this.yAxis = d3.svg
.axis()
.scale(self.y)
.tickSize(-this.height, 0)
.orient('left')
.tickFormat(d3.format(tickFormatY));
group
.append('g')
.attr('class', 'x axis')
.attr(
'transform',
'translate(0,' + (this.height - this.sizing.top) + ')'
)
.call(this.xAxis);
group
.append('g')
.attr('class', 'y axis')
.call(this.yAxis)
.selectAll('text')
.attr('y', -10)
.attr('x', 5)
.style('text-anchor', 'start');
};
LineChart.prototype._drawLine = function(group) {
var self = this;
group
.append('path')
.datum(this.data)
.attr('transform', 'translate(0,' + -this.sizing.top + ')')
.attr('class', 'line')
.attr('d', self.line);
};
LineChart.prototype._drawTicks = function() {
var self = this;
this.svg
.selectAll('circle.dot')
.data(this.data)
.enter()
.append('circle')
.attr('class', 'dot')
.attr('r', 5)
.attr('cx', function(d) {
return self.x(d[self.xKey]);
})
.attr('cy', function(d) {
return self.y(d[self.yKey]);
});
};
LineChart.prototype._drawAverages = function() {
var self = this;
var txtaverage;
var average =
_.reduce(
self.data,
function(memo, num) {
return memo + num.value;
},
0
) / self.data.length;
switch (self.unit) {
case 'hectares':
txtaverage = d3.format(',.0f')(average) + ' ' + self.unitname;
break;
case 'percentage':
txtaverage = d3.format('.2f')(average) + ' ' + self.unitname;
break;
case 'tg-c':
txtaverage = d3.format(',.2f')(average) + ' ' + self.unitname;
break;
case 'mt-co2':
txtaverage = d3.format(',.2f')(average) + ' ' + self.unitname;
break;
}
// Publish average to its parent (MultiLineChartIndicator)
self.parent.changeAverage([
{ average: txtaverage, color: self.color[0] }
]);
};
LineChart.prototype._drawTooltip = function() {
var self = this;
// Tooltip
this.tooltip = d3
.select('body')
.append('div')
.attr('class', 'linegraph-tooltip')
.style('visibility', 'hidden');
this.positioner = this.svg
.append('svg:line')
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', 0)
.attr('y2', this.height)
.style('visibility', 'hidden')
.style('stroke', '#DDD');
this.svg
.on('mouseenter', function() {
self.positioner.style('visibility', 'visible');
self.tooltip.style('visibility', 'visible');
})
.on('mousemove', function() {
var x0 = self.x.invert(d3.mouse(this)[0]);
self.setTooltip(x0, false);
mps.publish(
'LineChart/mousemove' + self.options.slug_compare + self.options.id,
[x0]
);
})
.on('mouseleave', function() {
self.positioner.style('visibility', 'hidden');
self.tooltip.style('visibility', 'hidden');
mps.publish(
'LineChart/mouseout' + self.options.slug_compare + self.options.id
);
});
};
LineChart.prototype.setTooltip = function(x0, is_reflect) {
var self = this;
var data = this.data;
var info = [];
var formatDate = d3.time.format('%Y');
var bisectDate = d3.bisector(function(d) {
return d.year;
}).left;
var x0 = x0,
i = bisectDate(self.data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = d0 && d1 && x0 - d0.year > d1.year - x0 ? d1 : d0;
if (!!d) {
var format = self.unit != 'percentage' ? '.3s' : '.2f',
xyear = self.x(d.year),
year = formatDate(d.year),
value = d3.format(format)(d.value) + ' ' + self.unitname;
// Positioner
self.positioner
.style('visibility', 'visible')
.attr('x1', xyear + self.sizing.left)
.attr('x2', xyear + self.sizing.left);
// Tooltip
self.tooltip
.transition()
.duration(125)
.style('visibility', 'visible');
info.push({ color: self.color[0], value: value, year: year });
self.tooltip
.classed('is-reflect', is_reflect)
// .html('<span class="date">' + year + '</span>' + value )
.html(this.templateTooltip({ year: info[0].year, tootip_info: info }))
.style('left', $(self.positioner[0]).offset().left + 'px')
.style('top', d3.event.pageY + 'px');
}
};
LineChart.prototype.setListeners = function() {
var formatDate = d3.time.format('%Y');
var bisectDate = d3.bisector(function(d) {
return d.year;
}).left;
var data = this.data;
var self = this;
mps.subscribe(
'LineChart/mouseout' + this.options.slug + this.options.id,
function() {
if (!!self.svg) {
self.positioner
.classed('is-reflect', false)
.style('visibility', 'hidden');
self.tooltip
.classed('is-reflect', false)
.style('visibility', 'hidden');
}
}
);
mps.subscribe(
'LineChart/mousemove' + this.options.slug + this.options.id,
function(x0) {
if (!!self.svg) {
self.setTooltip(x0, true);
}
}
);
};
LineChart.prototype.render = function() {
if (!!this.data.length && !!this.svg) {
var group = this.svg
.append('g')
.attr('class', 'focus')
.attr('width', this.width)
.attr('height', this.height)
.attr(
'transform',
'translate(' + this.sizing.left + ',' + this.sizing.top + ')'
);
this._drawAxes(group);
this._drawLine(group);
this._drawTicks();
this._drawTooltip();
this._drawAverages();
}
};
LineChart.prototype.destroy = function() {
if (!!this.tooltip) {
this.tooltip.remove();
}
};
return LineChart;
}
);