UNPKG

transcend-charts

Version:

Transcend is a charting and graph library for NUVI

947 lines (851 loc) 36.7 kB
'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;