transcend-charts
Version:
Transcend is a charting and graph library for NUVI
947 lines (851 loc) • 36.7 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _Numbers = require('../helpers/Numbers');
var _Numbers2 = _interopRequireDefault(_Numbers);
var _Charts = require('../helpers/Charts');
var _Charts2 = _interopRequireDefault(_Charts);
var _Color = require('./Color');
var _Color2 = _interopRequireDefault(_Color);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var StackedBarGraph = function StackedBarGraph(htmlContainer, graphData, graphOptions) {
var self = this;
var htmlCanvas = null;
var data = null;
var plotVars = {
graphArea: { top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0 } }; // this var is updated regularly in the render function to ensure it always contains correct data about the size and state of the graph
var DEBUG = false;
var fullScreenChangeListener;
var webkitFullScreenChangeListener;
var mozFullScreenChangeListener;
var msFullScreenChangeListener;
var resizeListener;
var isDestroyed = false;
var resizeTimer = null;
var options = graphOptions;
// background and padding
if (!options.backgroundColor) {
options.backgroundColor = new _Color2.default.Color('transparent');
} else {
options.backgroundColor = new _Color2.default.Color(options.backgroundColor);
}
if (!options.padding) {
options.padding = 0;
} else {
options.padding = parseFloat(options.padding);
}
if (!options.notchWidth) {
options.notchWidth = 10;
} else {
options.notchWidth = parseFloat(options.notchWidth);
}
if (!options.animation) {
options.animation = false;
}
// bar styles
if (!options.barFillColor) {
options.barFillColor = new _Color2.default.Color('#888888');
} else {
options.barFillColor = new _Color2.default.Color(options.barFillColor);
}
if (!options.barBorderColor) {
options.barBorderColor = new _Color2.default.Color('transparent');
} else {
options.barBorderColor = new _Color2.default.Color(options.barBorderColor);
}
if (!options.barSpacing) {
options.barSpacing = 1; // as a percent of the width of each bar
} else {
options.barSpacing = _Numbers2.default.makeNumber(options.barSpacing);
}
// grid and grid labels
if (!options.gridLineColor) {
options.gridLineColor = new _Color2.default.Color('#cccccc');
} else {
options.gridLineColor = new _Color2.default.Color(options.gridLineColor);
}
if (!options.gridFontSize) {
options.gridFontSize = 12;
} else if (typeof options.gridFontSize === 'string' && options.gridFontSize.indexOf('%') !== -1) {
options.gridFontSizeAsPct = parseFloat(options.gridFontSize) / 100;
} else {
options.gridFontSize = parseFloat(options.gridFontSize);
options.gridFontSizeAsPct = null;
}
if (!options.gridFontColor) {
options.gridFontColor = options.gridLineColor;
} else {
options.gridFontColor = new _Color2.default.Color(options.gridFontColor);
}
if (!options.gridFontFamily) {
options.gridFontFamily = 'Arial';
}
// labels on the bars
if (options.barLabelPosition !== 'bottom') {
options.barLabelPosition = 'above';
}
if (!options.barLabelFontSize) {
//options.barLabelFontSize = 12;
options.barLabelFontSize = 0.66;
} else if (typeof options.barLabelFontSize === 'string' && options.barLabelFontSize.indexOf('%') !== -1) {
options.barLabelFontSizeAsPct = parseFloat(options.barLabelFontSize) / 100;
} else {
options.barLabelFontSize = parseFloat(options.barLabelFontSize);
options.barLabelFontSizeAsPct = null;
}
if (!options.barLabelFontColor) {
options.barLabelFontColor = options.gridFontColor;
} else {
options.barLabelFontColor = new _Color2.default.Color(options.barLabelFontColor);
}
if (!options.barLabelFontFamily) {
options.barLabelFontFamily = 'Arial';
}
if (!options.barLabelFontWeight) {
options.barLabelFontWeight = '400';
}
// values floating on top of each bar
if (!options.valueFontColor) {
options.valueFontColor = options.gridFontColor;
}
if (!options.valueFontSize) {
//options.valueFontSize = 50;
options.valueFontSizeAsPct = 0.9;
} else if (typeof options.valueFontSize === 'string' && options.valueFontSize.indexOf('%') !== -1) {
options.valueFontSizeAsPct = parseFloat(options.valueFontSize) / 100;
} else {
options.valueFontSize = parseFloat(options.valueFontSize);
options.valueFontSizeAsPct = null;
}
if (!options.valueFontFamily) {
options.valueFontFamily = 'Arial';
}
if (!options.valueFontWeight) {
options.valueFontWeight = '400';
}
// stripes on the bars
if (!options.barStripeSpacing) {
options.barStripeSpacing = 10;
} else {
options.barStripeSpacing = parseFloat(options.barStripeSpacing);
}
if (!options.barStripeWidth) {
options.barStripeWidth = 2;
} else {
options.barStripeWidth = parseFloat(options.barStripeWidth);
}
if (!options.barStripeType) {
options.barStripeType = 'none';
}
if (['none', 'horizontal', 'vertical', 'diagonal'].indexOf(options.barStripeType) === -1) {
options.barStripeType = 'none';
}
if (!options.barStripeColor) {
options.barStripeColor = new _Color2.default.Color('#666666');
} else {
options.barStripeColor = new _Color2.default.Color(options.barStripeColor);
}
// legend
if (options.showLegend === undefined || options.showLegend === true) {
options.showLegend = true;
} else {
options.showLegend = false;
}
if (!options.legendBackgroundColor) {
options.legendBackgroundColor = new _Color2.default.Color('rgba(255,255,255,0.3');
} else {
options.legendBackgroundColor = new _Color2.default.Color(options.legendBackgroundColor);
}
if (!options.legendFontColor) {
options.legendFontColor = new _Color2.default.Color('#ffffff');
} else {
options.legendFontColor = new _Color2.default.Color(options.legendFontColor);
}
if (!options.legendFontFamily) {
options.legendFontFamily = 'Arial';
}
// TODO: make this capable of being a % as well
if (!options.legendFontSize) {
options.legendFontSize = 12;
} else {
options.legendFontSize = parseFloat(options.legendFontSize);
}
if (!options.legendFontWeight) {
options.legendFontWeight = '400';
}
if (options.abbreviateGridValues !== true) {
options.abbreviateGridValues = false;
}
var needsRender = false;
var barLabelSpacing = 2; // the spacing between the labels (when placed on the bottom) and the graph itself
var notchSpacing = 3;
var pxRatio = window.devicePixelRatio || 1;
var stackGap = 2;
var legendSpacing = 12;
var legendPadding = 8;
var legendLabelValueSpacing = 10;
var barLegendSpacing = 12;
var legendOutlineWidth = 1;
var legendOutlineCornerSize = 8;
this.render = function () {
if (!data) {
return false;
}
var ctx = htmlCanvas.getContext('2d');
// upscale this thang if the device pixel ratio is higher than 1
var backingStoreRatio = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1;
ctx.save();
if (pxRatio > 1) {
ctx.scale(pxRatio / backingStoreRatio, pxRatio / backingStoreRatio);
}
var canvasWidth = htmlCanvas.width / (pxRatio / backingStoreRatio);
var canvasHeight = htmlCanvas.height / (pxRatio / backingStoreRatio);
// fill background
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
if (options.backgroundColor.toString() !== 'transparent') {
ctx.fillStyle = options.backgroundColor.toString();
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
}
// calculate important measurements
var maxDataPoint = _lodash2.default.max(data, function (dataPoint) {
return _lodash2.default.sum(dataPoint.segments, function (segment) {
return segment.value;
});
});
plotVars.maxY = _lodash2.default.sum(maxDataPoint.segments, function (segment) {
return segment.value;
});
plotVars.minY = 0;
// if the grid font size was given as a percent, let's calculate the hard px value now
if (options.gridFontSizeAsPct) {
options.gridFontSize = options.gridFontSizeAsPct * (canvasHeight - options.padding * 2);
}
// calculate grid area
plotVars.graphArea = {
left: Number(options.padding) + Number(options.notchWidth) + Number(notchSpacing),
top: Number(options.padding) + options.gridFontSize / 2,
right: options.padding,
bottom: options.padding
};
if (options.barLabelPosition === 'bottom') {
plotVars.graphArea.bottom = Number(plotVars.graphArea.bottom) + Number(options.barLabelFontSize * 1.25) + Number(barLabelSpacing);
}
plotVars.graphArea.width = canvasWidth - plotVars.graphArea.left - plotVars.graphArea.right;
plotVars.graphArea.height = canvasHeight - plotVars.graphArea.top - plotVars.graphArea.bottom;
// calculate the nice round values for the y axis
var _HelpersCharts$yAxis = _Charts2.default.yAxis(plotVars.graphArea.height, plotVars.maxY, plotVars.minY, options.gridFontSize);
var numberOfLabels = _HelpersCharts$yAxis.numberOfLabels;
var valueBetweenLabels = _HelpersCharts$yAxis.valueBetweenLabels;
plotVars.chartMaxY = plotVars.minY + numberOfLabels * valueBetweenLabels;
// calculate the width of the y-value labels and adjust the graphArea to accommodate the longest one
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
ctx.font = options.gridFontSize + 'px ' + options.gridFontFamily;
var labelWidth = 0;
for (var i = 0; i <= numberOfLabels; i++) {
var lw = ctx.measureText(_Numbers2.default.formatNumber(String(i * valueBetweenLabels))).width;
if (lw > labelWidth) {
labelWidth = lw;
}
}
plotVars.graphArea.left = Number(options.padding) + Number(labelWidth) + Number(options.notchWidth) + notchSpacing;
plotVars.graphArea.width = canvasWidth - plotVars.graphArea.left - plotVars.graphArea.right;
var allowedBarWidth = plotVars.graphArea.width / data.length;
var barWidth = allowedBarWidth / (1 + options.barSpacing);
// draw horizontal grid lines
ctx.strokeStyle = options.gridLineColor.toString();
ctx.fillStyle = options.gridFontColor.toString();
for (var i = 0; i <= numberOfLabels; i++) {
var y = canvasHeight - plotVars.graphArea.bottom - i * valueBetweenLabels / plotVars.chartMaxY * plotVars.graphArea.height;
ctx.beginPath();
ctx.moveTo(plotVars.graphArea.left - options.notchWidth, y);
ctx.lineTo(canvasWidth - plotVars.graphArea.right, y);
ctx.stroke();
var labelstr = options.abbreviateGridValues ? _Numbers2.default.formatNumber(i * valueBetweenLabels, 1, true) : _Numbers2.default.formatNumber(i * valueBetweenLabels);
ctx.fillText(labelstr, plotVars.graphArea.left - options.notchWidth - notchSpacing, y);
}
// calculate widest bar LABEL
if (options.barLabelFontSizeAsPct) {
var fsize = 50;
var widestLabel = null;
var widestLabelWidth = 0;
ctx.font = options.barLabelFontWeight + ' ' + fsize + 'px ' + options.barLabelFontFamily;
for (var i = 0; i < data.length; i++) {
var width = ctx.measureText(data[i].name).width;
if (width > widestLabelWidth) {
widestLabel = data[i].name;
widestLabelWidth = width;
break;
}
}
// now we know which label is the widest. Let's iterate until we find a font size that fits the percent provided
var optimalBarLabelWidth = options.barLabelFontSizeAsPct * barWidth;
while (true) {
ctx.font = options.barLabelFontWeight + ' ' + fsize + 'px ' + options.barLabelFontFamily;
var width = ctx.measureText(widestLabel).width;
if (width <= optimalBarLabelWidth) {
break;
}
fsize = Math.floor(0.9 * fsize);
}
options.barLabelFontSize = fsize;
}
// calculate widest bar VALUE
if (options.valueFontSizeAsPct) {
var fsize = 50;
var widestValue = null;
var widestValueWidth = 0;
ctx.font = options.valueFontWeight + ' ' + fsize + 'px ' + options.valueFontFamily;
for (var i = 0; i < data.length; i++) {
var displayValue = data[i].displayValue;
if (!displayValue) {
var totalValue = _lodash2.default.sum(data[i].segments, function (segment) {
return segment.value;
});
displayValue = data[i].prefix + totalValue + data[i].suffix;
}
var width = ctx.measureText(displayValue).width;
if (width > widestValueWidth) {
widestValue = displayValue;
widestValueWidth = width;
break;
}
}
// now we know which label is the widest. Let's iterate until we find a font size that fits the percent provided
var optimalValueWidth = options.valueFontSizeAsPct * barWidth;
while (true) {
ctx.font = options.valueFontWeight + ' ' + fsize + 'px ' + options.valueFontFamily;
var width = ctx.measureText(widestValue).width;
if (width <= optimalValueWidth) {
break;
}
fsize = Math.floor(0.9 * fsize);
}
options.valueFontSize = fsize;
}
// determine if something is highlighted (which affects the rendering of all other elements)
var isSomethingHighlighted = false;
for (var i = 0; i < data.length; i++) {
if (data[i].isHighlighted) {
isSomethingHighlighted = true;
break;
}
}
// draw bars
for (var i = 0; i < data.length; i++) {
var x = plotVars.graphArea.left + i * allowedBarWidth + (allowedBarWidth - barWidth) / 2;
var y = canvasHeight - plotVars.graphArea.bottom;
data[i].boundingBox = { x: x, y: plotVars.graphArea.top, width: barWidth, height: plotVars.graphArea.height };
if (data[i].isHighlighted) {
ctx.fillStyle = new _Color2.default.Color(options.gridLineColor).fade(0.5);
ctx.fillRect(x, y - plotVars.graphArea.height, barWidth, plotVars.graphArea.height);
}
var isSomeSegmentHighlighted = data[i].segments.some(function (segment) {
return segment.isHighlighted;
});
for (var s = 0; s < data[i].segments.length; s++) {
var barHeight;
// bar
if (data[i].segments[s].value !== undefined) {
var barFillColor = new _Color2.default.Color(options.barFillColor);
if (data[i].segments[s].barFillColor) {
barFillColor = new _Color2.default.Color(data[i].segments[s].barFillColor);
}
if (isSomethingHighlighted && !data[i].isHighlighted) {
barFillColor.fade(0.7);
}
if (isSomeSegmentHighlighted && !data[i].segments[s].isHighlighted) {
barFillColor.fade(0.4);
}
ctx.fillStyle = barFillColor.toString();
barHeight = data[i].segments[s].value / plotVars.chartMaxY * plotVars.graphArea.height;
data[i].segments[s].boundingBox = { x: x, y: y - barHeight + stackGap / 2, width: barWidth, height: barHeight - stackGap };
ctx.fillRect(x, y - barHeight + stackGap / 2, barWidth, barHeight - stackGap);
// diagonal stripes
if (options.barStripeType !== 'none') {
ctx.save();
ctx.beginPath();
ctx.rect(x, y - barHeight + stackGap / 2, barWidth, barHeight - stackGap);
ctx.clip();
var barStripeColor = new _Color2.default.Color(options.barStripeColor);
if (data[i].segments[s].barStripeColor) {
barStripeColor = new _Color2.default.Color(data[i].segments[s].barStripeColor);
}
if (isSomethingHighlighted && !data[i].isHighlighted) {
barStripeColor.fade(0.7);
}
ctx.strokeStyle = barStripeColor.toString();
ctx.lineWidth = options.barStripeWidth;
ctx.beginPath();
switch (options.barStripeType) {
case 'diagonal':
for (var stripeY = y; stripeY > y - barHeight - barWidth; stripeY -= options.barStripeSpacing) {
ctx.moveTo(x, stripeY + barWidth);
ctx.lineTo(x + barWidth, stripeY);
}
break;
case 'horizontal':
for (var stripeY = y; stripeY > y - barHeight; stripeY -= options.barStripeSpacing) {
ctx.moveTo(x, stripeY);
ctx.lineTo(x + barWidth, stripeY);
}
break;
case 'vertical':
var stripeY = y - barHeight;
for (var m = x; m < x + barWidth; m += options.barStripeSpacing) {
ctx.moveTo(m, stripeY);
ctx.lineTo(m, stripeY + barHeight);
}
break;
}
ctx.stroke();
ctx.restore();
}
// draw border around bar segments
if (data[i].segments[s].barBorderColor || options.barBorderColor.toString() !== 'transparent') {
ctx.beginPath();
var barBorderColor = new _Color2.default.Color(options.barBorderColor);
if (data[i].segments[s].barBorderColor) {
barBorderColor = new _Color2.default.Color(data[i].segments[s].barBorderColor);
}
if (isSomethingHighlighted && !data[i].isHighlighted) {
barBorderColor.fade(0.7);
}
ctx.strokeStyle = barBorderColor.toString();
ctx.rect(x, y - barHeight + stackGap / 2, barWidth, barHeight - stackGap);
ctx.stroke();
}
}
// move y for the next piece of stacked graph
y -= barHeight;
}
// bar label
if (data[i].name !== undefined) {
ctx.fillStyle = options.barLabelFontColor.toString();
if (options.barLabelPosition === 'bottom') {
ctx.font = options.barLabelFontSize + 'px ' + options.barLabelFontFamily;
ctx.textBaseline = 'top';
ctx.textAlign = 'center';
ctx.fillText(data[i].name, x + barWidth / 2, canvasHeight - plotVars.graphArea.bottom + barLabelSpacing);
} else {
var fsize = 12;
if (barWidth < 20) {
fsize = Math.ceil((barWidth + options.barSpacing * barWidth) / 2);
} else {
fsize = Math.ceil(barWidth / 2);
}
if (fsize > 14) {
fsize = 14;
}
ctx.font = fsize + 'px ' + options.barLabelFontFamily;
ctx.textBaseline = 'middle';
ctx.textAlign = 'left';
ctx.save();
ctx.translate(x + barWidth / 2, canvasHeight - plotVars.graphArea.bottom - barWidth / 4);
ctx.rotate(-Math.PI / 2);
ctx.fillText(data[i].name, 0, 0);
ctx.restore();
}
}
}
// highlighted bar and show legend (if applicable)
for (var i = 0; i < data.length; i++) {
if (data[i].isHighlighted) {
// some vars for the highlight
var barX = plotVars.graphArea.left + i * allowedBarWidth + (allowedBarWidth - barWidth) / 2;
var barY = canvasHeight - plotVars.graphArea.bottom;
var legendY = canvasHeight - plotVars.graphArea.bottom;
var legendX = barX + barWidth + barLegendSpacing;
// value at the top
ctx.fillStyle = options.valueFontColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.font = options.valueFontWeight + ' ' + options.valueFontSize + 'px ' + options.valueFontFamily;
var totalDisplayValue = data[i].displayValue;
if (!totalDisplayValue) {
var totalValue = _lodash2.default.sum(data[i].segments, function (segment) {
return segment.value;
});
totalDisplayValue = data[i].prefix + totalValue + data[i].suffix;
}
ctx.fillText(totalDisplayValue, barX + barWidth / 2, plotVars.graphArea.top);
// determine whether the legends should appear on the right or left of the bar
ctx.font = options.legendFontWeight + ' ' + options.legendFontSize + 'px ' + options.legendFontFamily;
var widestLegendLabel = _lodash2.default.max(data[i].segments, function (segment) {
return ctx.measureText(segment.name).width;
});
var widestLegendLabelWidth = ctx.measureText(widestLegendLabel.name).width;
var widestLegendValue = _lodash2.default.max(data[i].segments, function (segment) {
return ctx.measureText(data[i].prefix + segment.value + data[i].suffix).width;
});
var widestLegendValueWidth = ctx.measureText(data[i].prefix + widestLegendValue.value + data[i].suffix).width;
var widestLegendWidth = widestLegendLabelWidth + legendLabelValueSpacing + widestLegendValueWidth;
var legendPosition = 'right';
if (barX + barWidth + barLegendSpacing + widestLegendWidth >= canvasWidth - plotVars.graphArea.right) {
legendPosition = 'left';
}
// iterate over segments for this bar and render their legends
for (var s = 0; s < data[i].segments.length; s++) {
var segmentColor = new _Color2.default.Color(options.barFillColor);
if (data[i].segments[s].barFillColor) {
segmentColor = new _Color2.default.Color(data[i].segments[s].barFillColor);
}
var widthOfLabel = ctx.measureText(data[i].segments[s].name).width;
var displayValue = data[i].segments[s].displayValue;
if (!displayValue) {
displayValue = data[i].prefix + data[i].segments[s].value + data[i].suffix;
}
var widthOfValue = ctx.measureText(displayValue).width;
var legendHeight = options.legendFontSize + legendPadding * 2;
var legendWidth = widthOfLabel + legendLabelValueSpacing + widthOfValue + legendPadding * 2;
barHeight = data[i].segments[s].value / plotVars.chartMaxY * plotVars.graphArea.height;
if (legendPosition === 'left') {
legendX = barX - barLegendSpacing - legendWidth;
}
// draw the callout polygons that connect the legends to their segments
ctx.fillStyle = new _Color2.default.Color(segmentColor).fade(0.5).toString();
ctx.beginPath();
if (legendPosition === 'right') {
ctx.moveTo(legendX, legendY - legendHeight);
ctx.lineTo(legendX, legendY);
ctx.lineTo(barX + barWidth, barY);
ctx.lineTo(barX + barWidth, barY - barHeight);
} else {
ctx.moveTo(legendX + legendWidth, legendY - legendHeight);
ctx.lineTo(legendX + legendWidth, legendY);
ctx.lineTo(barX, barY);
ctx.lineTo(barX, barY - barHeight);
}
ctx.closePath();
ctx.fill();
// legend background
ctx.fillRect(legendX, legendY - legendHeight, legendWidth, legendHeight);
// draw four corners
ctx.strokeStyle = segmentColor.toString();
ctx.lineWidth = legendOutlineWidth;
ctx.beginPath();
ctx.moveTo(legendX + legendOutlineWidth, legendY - legendHeight + legendOutlineCornerSize);
ctx.lineTo(legendX + legendOutlineWidth, legendY - legendHeight + legendOutlineWidth);
ctx.lineTo(legendX + legendOutlineCornerSize, legendY - legendHeight + legendOutlineWidth);
ctx.moveTo(legendX + legendWidth - legendOutlineCornerSize, legendY - legendHeight + legendOutlineWidth);
ctx.lineTo(legendX + legendWidth - legendOutlineWidth, legendY - legendHeight + legendOutlineWidth);
ctx.lineTo(legendX + legendWidth - legendOutlineWidth, legendY - legendHeight + legendOutlineCornerSize);
ctx.moveTo(legendX + legendWidth - legendOutlineWidth, legendY - legendOutlineCornerSize);
ctx.lineTo(legendX + legendWidth - legendOutlineWidth, legendY - legendOutlineWidth);
ctx.lineTo(legendX + legendWidth - legendOutlineCornerSize, legendY - legendOutlineWidth);
ctx.moveTo(legendX + legendOutlineCornerSize, legendY - legendOutlineWidth);
ctx.lineTo(legendX + legendOutlineWidth, legendY - legendOutlineWidth);
ctx.lineTo(legendX + legendOutlineWidth, legendY - legendOutlineCornerSize);
ctx.stroke();
ctx.fillStyle = options.legendFontColor.toString();
ctx.textBaseline = 'top';
ctx.textAlign = 'left';
ctx.fillText(data[i].segments[s].name, legendX + legendPadding, legendY - legendHeight + legendPadding);
ctx.fillStyle = segmentColor.toString();
var displayValue = data[i].segments[s].displayValue;
if (!displayValue) {
displayValue = data[i].prefix + data[i].segments[s].value + data[i].suffix;
}
ctx.fillText(displayValue, legendX + legendPadding + widthOfLabel + legendLabelValueSpacing, legendY - legendHeight + legendPadding);
legendY -= legendHeight + legendSpacing;
barY -= barHeight;
}
}
}
// draw fps
if (DEBUG) {
ctx.fillStyle = '#666666';
ctx.fillRect(canvasWidth - 40, canvasHeight - 15, 40, 15);
ctx.textAlign = 'right';
ctx.textBaseline = 'bottom';
ctx.fillStyle = '#000000';
ctx.font = '11px sans-serif';
ctx.fillText(String(Math.round(fps)) + ' fps', canvasWidth - 5, canvasHeight - 1);
}
ctx.restore();
};
/***
* Handles mouseout
***/
function handleMouseOut() {
unhighlightAll();
needsRender = true;
}
function handleMouseMove(event, pt) {
// mark all bars as not highlighted
unhighlightAll();
var hoveredGroup = hitTestGroups(pt, function (group) {
group.isHighlighted = true;
}, function (group) {
group.isHighlighted = false;
});
var hoveredSegment = hitTestSegments(pt, function (segment) {
segment.isHighlighted = true;
}, function (segment) {
segment.isHighlighted = false;
});
needsRender = true;
}
function handleMouseUp(event, pt) {
var hoveredSegment = hitTestSegments(pt, null, null);
if (hoveredSegment && hoveredSegment.onClick) {
hoveredSegment.onClick(event);
}
}
function unhighlightAll() {
data.forEach(function (group) {
group.isHighlighted = false;
group.segments.forEach(function (segment) {
segment.isHighlighted = false;
});
});
}
function hitTestGroups(pt, onHit, onMiss) {
var hoveredGroup = null;
data.forEach(function (group, i) {
if (pt.x >= group.boundingBox.x && pt.x < group.boundingBox.x + group.boundingBox.width && pt.y >= group.boundingBox.y && pt.y < group.boundingBox.y + group.boundingBox.height) {
hoveredGroup = group;
if (onHit) {
onHit(group);
}
} else {
if (onMiss) {
onMiss(group);
}
}
});
return hoveredGroup;
}
function hitTestSegments(pt, onHit, onMiss) {
var hoveredSegment = null;
data.forEach(function (group, i) {
if (pt.x >= group.boundingBox.x && pt.x < group.boundingBox.x + group.boundingBox.width && pt.y >= group.boundingBox.y && pt.y < group.boundingBox.y + group.boundingBox.height) {
group.segments.forEach(function (segment, s) {
if (pt.x >= segment.boundingBox.x && pt.x < segment.boundingBox.x + segment.boundingBox.width && pt.y >= segment.boundingBox.y && pt.y < segment.boundingBox.y + segment.boundingBox.height) {
if (!hoveredSegment) {
hoveredSegment = segment;
}
if (onHit) {
onHit(segment);
}
} else {
if (onMiss) {
onMiss(segment);
}
}
});
}
});
return hoveredSegment;
}
/***
* Handles events related to mouse movement
* This method sets the appropriate variables to update the view.
* It does not call this.render() for every time the mouse moves,
* instead it relies on the next natural render cycle to update the view
***/
/*function handleMouseMove(pt) {
// mark all bars as not highlighted
for (var i=0; i < data.length; i++) {
data[i].isHighlighted = false;
}
var ctx = htmlCanvas.getContext("2d");
var backingStoreRatio = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1;
var canvasWidth = htmlCanvas.width/(pxRatio/backingStoreRatio);
var canvasHeight = htmlCanvas.height/(pxRatio/backingStoreRatio);
var allowedBarWidth = plotVars.graphArea.width / data.length;
var barWidth = allowedBarWidth / (1 + options.barSpacing);
// hit test all bars
for (var i=0; i < data.length; i++) {
var barX = plotVars.graphArea.left + allowedBarWidth * i + (allowedBarWidth - barWidth)/2;
// mark as highlighted
if (pt.x >= barX && pt.x < barX + barWidth && pt.y >= plotVars.graphArea.top && pt.y < canvasHeight - plotVars.graphArea.bottom) {
data[i].isHighlighted = true;
}
}
needsRender = true;
}*/
/***
* Initialize some basic vars including the html canvas to render on
***/
function _init() {
var _this = this;
htmlCanvas = document.createElement('CANVAS');
htmlContainer.appendChild(htmlCanvas);
self.setData(graphData);
setTimeout(fillParent, 1);
if (options.animation) {
for (var i = 0; i < data.length; i++) {
data[i].animationFinished = false;
}
}
needsRender = true;
resizeListener = window.addEventListener('resize', function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
fillParent.call(_this);
}, 50);
});
fullScreenChangeListener = window.addEventListener('webkitfullscreenchange', fullscreenChange);
webkitFullScreenChangeListener = window.addEventListener('fullscreenchange', fullscreenChange);
mozFullScreenChangeListener = window.addEventListener('mozfullscreenchange', fullscreenChange);
msFullScreenChangeListener = window.addEventListener('msfullscreenchange', fullscreenChange);
function fullscreenChange(event) {
var fullscreenElement = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement;
if (!fullscreenElement) {
// exiting full screen
var targetElement = event.target;
// if this element contains a canvas, make it tiny so we don't mess up the size of its parent
// don't worry, the window.resize event that fires next will make it fill its parent just fine
var canvases = targetElement.getElementsByTagName('CANVAS');
for (var c = 0; c < canvases.length; c++) {
if (canvases[c] === htmlCanvas) {
canvases[c].width = 1;
canvases[c].height = 1;
canvases[c].style.width = '1px';
canvases[c].style.height = '1px';
break;
}
}
}
}
if (options.showLegend) {
htmlCanvas.addEventListener('mousemove', function (event) {
var rect = htmlCanvas.getBoundingClientRect();
var pt = {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
handleMouseMove(event, pt);
});
htmlCanvas.addEventListener('mouseup', function (event) {
var rect = htmlCanvas.getBoundingClientRect();
var pt = {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
handleMouseUp(event, pt);
});
htmlCanvas.addEventListener('mouseout', function (event) {
handleMouseOut(event);
});
}
if (window.requestAnimationFrame) {
window.requestAnimationFrame(animateFrame);
} else if (window.webkitRequestAnimationFrame) {
window.webkitRequestAnimationFrame(animateFrame);
} else if (window.mozRequestAnimationFrame) {
window.mozRequestAnimationFrame(animateFrame);
} else if (window.oRequestAnimationFrame) {
window.oRequestAnimationFrame(animateFrame);
}
}
/***
* Fills the parent container with this canvas element
***/
function fillParent() {
if (htmlCanvas && htmlCanvas.parentNode) {
var style = window.getComputedStyle(htmlCanvas.parentNode);
var width = htmlCanvas.parentNode.offsetWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
var height = htmlCanvas.parentNode.offsetHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom);
// upscale this thang if the device pixel ratio is higher than 1
htmlCanvas.width = width * pxRatio;
htmlCanvas.height = height * pxRatio;
htmlCanvas.style.width = width + 'px';
htmlCanvas.style.height = height + 'px';
}
needsRender = true;
}
/***
* This function sets/resets the data for the graph
***/
this.setData = function (someData) {
data = someData.slice();
// let's create a variable for each bar so we can track animation
if (data && data.length) {
for (var i = 0; i < data.length; i++) {
for (var s = 0; s < data[i].segments.length; s++) {
var parts = _Numbers2.default.separateNumberUnits(data[i].segments[s].value);
if (data[i].segments[s].prefix === undefined) {
data[i].segments[s].prefix = parts.prefix;
}
if (data[i].segments[s].suffix === undefined) {
data[i].segments[s].suffix = parts.suffix;
}
data[i].segments[s].value = parts.value;
if (!data[i].prefix) {
data[i].prefix = data[i].segments[s].prefix;
}
if (!data[i].suffix) {
data[i].suffix = data[i].segments[s].suffix;
}
if (data[i].segments[s].barFillColor) {
data[i].segments[s].barFillColor = new _Color2.default.Color(data[i].segments[s].barFillColor);
}
if (data[i].segments[s].barBorderColor) {
data[i].segments[s].barBorderColor = new _Color2.default.Color(data[i].segments[s].barBorderColor);
}
if (data[i].segments[s].barStripeColor) {
data[i].segments[s].barStripeColor = new _Color2.default.Color(data[i].segments[s].barStripeColor);
}
}
}
}
};
/***
* The animateFrame method is called many times per second to calculate any
* movement needed in the graph. It then calls render() to update the display.
* Even if there is no animation in the graph this method ensures the view is updated frequently in case
* mouse events change the view
***/
function animateFrame() {
var thisFrame = new Date().getTime();
var elapsed = thisFrame - lastFrame; // elapsed time since last render
fps = 1000 / elapsed;
if (needsRender) {
self.render();
needsRender = false;
}
lastFrame = thisFrame;
if (!isDestroyed) {
if (window.requestAnimationFrame) {
window.requestAnimationFrame(animateFrame);
} else if (window.webkitRequestAnimationFrame) {
window.webkitRequestAnimationFrame(animateFrame);
} else if (window.mozRequestAnimationFrame) {
window.mozRequestAnimationFrame(animateFrame);
} else if (window.oRequestAnimationFrame) {
window.oRequestAnimationFrame(animateFrame);
}
}
}
var lastFrame = new Date().getTime(); // the timestamp of the last time the frame was last rendered
var fps = 0;
this.destroy = function () {
if (fullScreenChangeListener) {
window.removeEventListener('fullscreenchange', fullScreenChangeListener);
}
if (webkitFullScreenChangeListener) {
window.removeEventListener('webkitfullscreenchange', webkitFullScreenChangeListener);
}
if (mozFullScreenChangeListener) {
window.removeEventListener('mozfullscreenchange', mozFullScreenChangeListener);
}
if (msFullScreenChangeListener) {
window.removeEventListener('msfullscreenchange', msFullScreenChangeListener);
}
if (resizeListener) {
window.removeEventListener('resize', resizeListener);
}
if (htmlCanvas && htmlCanvas.parentNode) {
htmlCanvas.parentNode.removeChild(htmlCanvas);
}
isDestroyed = true;
};
// Initialize
_init.call(this);
};
exports.default = StackedBarGraph;