UNPKG

transcend-charts

Version:

Transcend is a charting and graph library for NUVI

826 lines (761 loc) 36.2 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 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 }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var GroupedBarGraph = function () { function GroupedBarGraph(el, graphData) { var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; _classCallCheck(this, GroupedBarGraph); this.needsRender = true; this.el = el; this.lastFrame = new Date().getTime(); this.plotVars = {}; this.graphArea = {}; this.hits = {}; this.isDestroyed = false; this.autoBind(); this.setOptions(options); this.setData(graphData); this.build(); this.attach(); this.onAnimateFrame(); window.setTimeout(this.fillParent.call(this), 0); } _createClass(GroupedBarGraph, [{ key: 'autoBind', value: function autoBind() { this.onAnimateFrame = this.onAnimateFrame.bind(this); this.onFullscreenChange = this.onFullscreenChange.bind(this); this.onWindowResize = this.onWindowResize.bind(this); this.onMouseOut = this.onMouseOut.bind(this); this.onMouseMove = this.onMouseMove.bind(this); this.onClick = this.onClick.bind(this); } }, { key: 'build', value: function build() { this.htmlCanvas = document.createElement('CANVAS'); this.el.appendChild(this.htmlCanvas); this.ctx = this.htmlCanvas.getContext('2d'); } }, { key: 'attach', value: function attach() { window.addEventListener('webkitfullscreenchange', this.onFullscreenChange); window.addEventListener('fullscreenchange', this.onFullscreenChange); window.addEventListener('mozfullscreenchange', this.onFullscreenChange); window.addEventListener('msfullscreenchange', this.onFullscreenChange); window.addEventListener('resize', this.onWindowResize); this.htmlCanvas.addEventListener('mousemove', this.onMouseMove); this.htmlCanvas.addEventListener('mouseout', this.onMouseOut); this.htmlCanvas.addEventListener('click', this.onClick); } }, { key: 'destroy', value: function destroy() { this.detach(); this.el.removeChild(this.htmlCanvas); this.isDestroyed = true; } }, { key: 'detach', value: function detach() { window.removeEventListener('webkitfullscreenchange', this.onFullscreenChange); window.removeEventListener('fullscreenchange', this.onFullscreenChange); window.removeEventListener('mozfullscreenchange', this.onFullscreenChange); window.removeEventListener('msfullscreenchange', this.onFullscreenChange); window.removeEventListener('resize', this.onWindowResize); this.htmlCanvas.removeEventListener('mousemove', this.onMouseMove); this.htmlCanvas.removeEventListener('mouseout', this.onMouseOut); this.htmlCanvas.removeEventListener('click', this.onClick); } /** * This function sets/resets the data for the graph **/ }, { key: 'setData', value: function setData() { var _this = this; var someData = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; // let's create a variable for each bar so we can track animation this.data = _lodash2.default.map(someData, function (barGroup) { var barData = Object.assign({}, barGroup); barData.segments = _this.processSegmentData(barData.segments, barData); return barData; }); } }, { key: 'processSegmentData', value: function processSegmentData(segments, barGroup) { return _lodash2.default.map(segments, function (segmentData) { var segment = Object.assign({}, segmentData || {}); var parts = _Numbers2.default.separateNumberUnits(segment.value); if (segment.prefix === undefined) { segment.prefix = parts.prefix; } if (segment.suffix === undefined) { segment.suffix = parts.suffix; } segment.value = parts.value; if (!barGroup.prefix) { barGroup.prefix = segment.prefix; } if (!barGroup.suffix) { barGroup.suffix = segment.suffix; } if (segment.barFillColor) { segment.barFillColor = new _Color2.default.Color(segment.barFillColor); } if (segment.barBorderColor) { segment.barBorderColor = new _Color2.default.Color(segment.barBorderColor); } if (segment.barStripeColor) { segment.barStripeColor = new _Color2.default.Color(segment.barStripeColor); } return segment; }); } }, { key: 'fillParent', value: function fillParent() { if (this.htmlCanvas && this.htmlCanvas.parentNode) { this.needsRender = true; var pxRatio = window.devicePixelRatio || 1; var style = window.getComputedStyle(this.htmlCanvas.parentNode); var width = this.htmlCanvas.parentNode.offsetWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight); var height = this.htmlCanvas.parentNode.offsetHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom); // upscale this thang if the device pixel ratio is higher than 1 this.htmlCanvas.width = width * pxRatio; this.htmlCanvas.height = height * pxRatio; this.htmlCanvas.style.width = width + 'px'; this.htmlCanvas.style.height = height + 'px'; } } }, { key: 'isPercentage', value: function isPercentage(x) { var str = String(x); return str.substr(-1) === '%'; } }, { key: 'setOptions', value: function setOptions(options) { this.options = Object.assign({}, { abbreviateGridValues: false, animation: false, backgroundColor: 'transparent', barBorderColor: 'transparent', barFillColor: '#888888', barLabelFontColor: false, barLabelFontFamily: 'Arial', barLabelFontSize: '66%', barLabelFontWeight: '400', barLabelOrientation: 'horizontal', barLabelSpacing: 3, // the spacing between the labels (when placed on the bottom) and the graph itself barLegendSpacing: 12, barSpacing: 1, // as a percent of the width of each ba, barStripeColor: '#666666', barStripeSpacing: 10, barStripeType: 'none', barStripeWidth: 2, colorBarWidth: 12, colorBarSpacing: 5, // the pixel spacing between a color swatch on the legend and a label gridFontColor: false, gridFontFamily: 'Arial', gridFontSize: 12, gridFontSizeAsPct: false, gridLineColor: '#cccccc', groupSpacing: 1, // as a percent of the width of each bar legendBackgroundColor: 'rgba(255,255,255,0.3)', legendFontColor: '#ffffff', legendFontFamily: 'Arial', legendFontSize: 12, legendFontWeight: '400', legendLabelValueSpacing: 10, legendOutlineColor: 'transparent', legendOutlineCornerSize: 8, // the width of the little corner notches around the legend legendOutlineWidth: 1, // the width of the stroke around the legend notchSpacing: 3, notchWidth: 10, padding: 0, showLegend: true, showValues: true, tooltipPadding: 12, // the padding for the tooltip callout used on mouseover valueFontColor: false, valueFontFamily: 'Arial', valueFontSize: '90%', valueFontSizeAsPct: false, valueFontWeight: '400' }, options); this.colorizeOptions(); this.percentageOptions(); this.pixelOptions(); } }, { key: 'onWindowResize', value: function onWindowResize() { this.fillParent(); } }, { key: 'onClick', value: function onClick(event) { var rect = this.htmlCanvas.getBoundingClientRect(); var pt = { x: event.clientX - rect.left, y: event.clientY - rect.top }; var hits = this.getHitsFromPoint(pt); if (hits.segment && hits.segment.onClick) { hits.segment.onClick(event); } } }, { key: 'onAnimateFrame', value: function onAnimateFrame() { var thisFrame = new Date().getTime(); var elapsed = thisFrame - this.lastFrame; // elapsed time since last render this.fps = 1000 / elapsed; if (!this.isDestroyed) { if (this.needsRender) { this.render(); this.needsRender = false; } this.lastFrame = thisFrame; if (window.requestAnimationFrame) { window.requestAnimationFrame(this.onAnimateFrame); } else if (window.webkitRequestAnimationFrame) { window.webkitRequestAnimationFrame(this.onAnimateFrame); } else if (window.mozRequestAnimationFrame) { window.mozRequestAnimationFrame(this.onAnimateFrame); } else if (window.oRequestAnimationFrame) { window.oRequestAnimationFrame(this.onAnimateFrame); } } } }, { key: 'onFullscreenChange', value: function onFullscreenChange() { 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] === this.htmlCanvas) { canvases[c].width = 1; canvases[c].height = 1; canvases[c].style.width = '1px'; canvases[c].style.height = '1px'; break; } } } } }, { key: 'onMouseOut', value: function onMouseOut() { this.unhighlightAll(); } }, { key: 'onMouseMove', value: function onMouseMove(event) { if (!this.options.showLegend) { return; } var rect = this.htmlCanvas.getBoundingClientRect(); var pt = { x: event.clientX - rect.left, y: event.clientY - rect.top }; this.hits = this.getHitsFromPoint(pt); this.needsRender = true; } }, { key: 'percentageOptions', value: function percentageOptions() { var _this2 = this; ['gridFontSize', 'valueFontSize', 'barLabelFontSize'].forEach(function (key) { var val = _this2.options[key]; if (_this2.isPercentage(val)) { _this2.options[key + 'AsPct'] = parseFloat(val.substr(0, val.length - 1)) / 100; } else { _this2.options[key] = parseFloat(val); _this2.options[key + 'AsPct'] = false; } }); } }, { key: 'colorizeOptions', value: function colorizeOptions() { var _this3 = this; var keys = ['backgroundColor', 'barBorderColor', 'barFillColor', 'barStripeColor', 'gridFontColor', 'gridLineColor', 'legendBackgroundColor', 'legendOutlineColor', 'legendFontColor', 'valueFontColor']; keys.forEach(function (key) { var color = _this3.options[key]; if (color) { _this3.options[key] = new _Color2.default.Color(color); } }); } }, { key: 'pixelOptions', value: function pixelOptions() { var _this4 = this; var keys = ['colorBarSpacing', 'colorBarWidth', 'barLabelSpacing', 'barStripeWidth', 'gridFontSize', 'legendFontSize', 'legendLabelValueSpacing', 'legendOutlineCornerSize', 'legendOutlineWidth', 'notchSpacing', 'notchWidth', 'padding', 'tooltipPadding']; keys.forEach(function (key) { _this4.options[key] = parseFloat(_this4.options[key]); }); } }, { key: 'getWidestLabel', value: function getWidestLabel() { var fsize = 50; var widestLabel = null; var widestLabelWidth = 0; this.ctx.font = this.options.barLabelFontWeight + ' ' + fsize + 'px ' + this.options.barLabelFontFamily; for (var ii = 0; ii < this.data.length; ii++) { var width = this.ctx.measureText(this.data[ii].name).width; if (width > widestLabelWidth) { widestLabel = this.data[ii].name; widestLabelWidth = width; } } return { label: widestLabel, width: widestLabelWidth }; } }, { key: 'calculateBarLabelFontSize', value: function calculateBarLabelFontSize(widestLabelText, optimalBarLabelWidth) { // now we know which label is the widest. Let's iterate until we find a font size that fits the size provided var fsize = optimalBarLabelWidth; var diff = 1; while (diff > 0) { this.ctx.font = this.options.barLabelFontWeight + ' ' + fsize + 'px ' + this.options.barLabelFontFamily; var width = this.ctx.measureText(widestLabelText).width; diff = width - optimalBarLabelWidth; if (diff > 0) { fsize = Math.floor(0.9 * fsize); } } return fsize; } }, { key: 'render', value: function render(data) { if (data) { this.setData(data); } if (!this.needsRender) { return; } var options = this.options; var pxRatio = window.devicePixelRatio || 1; var backingStoreRatio = this.ctx.webkitBackingStorePixelRatio || this.ctx.mozBackingStorePixelRatio || this.ctx.msBackingStorePixelRatio || this.ctx.oBackingStorePixelRatio || this.ctx.backingStorePixelRatio || 1; var canvasWidth = this.htmlCanvas.width / (pxRatio / backingStoreRatio); var canvasHeight = this.htmlCanvas.height / (pxRatio / backingStoreRatio); this.plotVars.canvasHeight = canvasHeight; this.plotVars.canvasWidth = canvasWidth; // upscale this thang if the device pixel ratio is higher than 1 this.ctx.save(); if (pxRatio > 1) { this.ctx.scale(pxRatio / backingStoreRatio, pxRatio / backingStoreRatio); } this.graphArea = { top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0 }; // fill background this.ctx.clearRect(0, 0, canvasWidth, canvasHeight); if (options.backgroundColor.toString() !== 'transparent') { this.ctx.fillStyle = this.options.backgroundColor.toString(); this.ctx.fillRect(0, 0, canvasWidth, canvasHeight); } // calculate important measurements var maxDataPoint = _lodash2.default.max(this.data, function (dataPoint) { return _lodash2.default.max(dataPoint.segments, function (segment) { return segment.value; }).value; }); this.plotVars.maxY = _lodash2.default.max(maxDataPoint.segments, function (segment) { return segment.value; }).value; this.plotVars.minY = 0; // if the grid font size was given as a percent, let's calculate the hard px value now if (this.options.gridFontSizeAsPct) { this.options.gridFontSize = this.options.gridFontSizeAsPct * (canvasHeight - this.options.padding * 2); } // calculate grid area this.graphArea = { left: this.options.padding + Number(this.options.notchWidth) + Number(this.options.notchSpacing), top: this.options.padding, right: this.options.padding, bottom: this.options.padding }; this.graphArea.width = canvasWidth - this.graphArea.left - this.graphArea.right; // calculate the nice round values for the y axis var _HelpersCharts$yAxis = _Charts2.default.yAxis(this.graphArea.height, this.plotVars.maxY, this.plotVars.minY, this.options.gridFontSize); var numberOfLabels = _HelpersCharts$yAxis.numberOfLabels; var valueBetweenLabels = _HelpersCharts$yAxis.valueBetweenLabels; this.plotVars.chartMaxY = this.plotVars.minY + numberOfLabels * valueBetweenLabels; // calculate the width of the y-value labels and adjust the graphArea to accommodate the longest one this.ctx.textAlign = 'right'; this.ctx.textBaseline = 'middle'; this.ctx.font = this.options.gridFontSize + 'px ' + this.options.gridFontFamily; var labelWidth = 0; for (var i = 0; i <= numberOfLabels; i++) { var labelstr = this.options.abbreviateGridValues ? _Numbers2.default.formatNumber(i * valueBetweenLabels, 1, true) : _Numbers2.default.formatNumber(i * valueBetweenLabels); var lw = this.ctx.measureText(labelstr).width; if (lw > labelWidth) { labelWidth = lw; } } this.graphArea.left = Number(this.options.padding) + Number(labelWidth) + Number(this.options.notchWidth) + this.options.notchSpacing; this.graphArea.width = canvasWidth - this.graphArea.left - this.graphArea.right; var allowedGroupWidth = this.graphArea.width / this.data.length; var groupWidth = allowedGroupWidth / (1 + this.options.groupSpacing); var maxBarsPerGroup = 0; var allowedBarWidth = 0; if (this.data.length) { maxBarsPerGroup = _lodash2.default.max(this.data, function (datum) { return datum.segments.length; }).segments.length; } allowedBarWidth = groupWidth / maxBarsPerGroup; var barWidth = allowedBarWidth / (1 + this.options.barSpacing); // calculate where the bottom of the graph should be var widestLabel = this.getWidestLabel(); if (this.options.barLabelOrientation === 'horizontal') { if (this.options.barLabelFontSizeAsPct) { this.options.barLabelFontSize = this.calculateBarLabelFontSize(widestLabel.label, this.options.barLabelFontSizeAsPct * groupWidth); } this.graphArea.bottom = this.options.padding + Number(this.options.barLabelFontSize * 1.25) + Number(this.options.barLabelSpacing); } else { if (this.options.barLabelFontSizeAsPct) { this.options.barLabelFontSize = this.calculateBarLabelFontSize(widestLabel.label, this.options.barLabelFontSizeAsPct * (canvasHeight - 2 * this.options.padding)); } this.ctx.font = this.options.barLabelFontWeight + ' ' + this.options.barLabelFontSize + 'px ' + this.options.barLabelFontFamily; this.graphArea.bottom = this.options.padding + Number(this.ctx.measureText(widestLabel.label).width) + Number(this.options.barLabelSpacing); } this.graphArea.height = canvasHeight - this.graphArea.top - this.graphArea.bottom; // draw horizontal grid lines this.ctx.font = this.options.gridFontSize + 'px ' + this.options.gridFontFamily; this.ctx.strokeStyle = this.options.gridLineColor.toString(); this.ctx.fillStyle = (this.options.gridFontColor || this.options.gridLineColor).toString(); for (var _i = 0; _i <= numberOfLabels; _i++) { var y = canvasHeight - this.graphArea.bottom - _i * valueBetweenLabels / this.plotVars.chartMaxY * this.graphArea.height; this.ctx.beginPath(); this.ctx.moveTo(this.graphArea.left - this.options.notchWidth, y); this.ctx.lineTo(canvasWidth - this.graphArea.right, y); this.ctx.stroke(); var _labelstr = this.options.abbreviateGridValues ? _Numbers2.default.formatNumber(_i * valueBetweenLabels, 1, true) : _Numbers2.default.formatNumber(_i * valueBetweenLabels); this.ctx.fillText(_labelstr, this.graphArea.left - this.options.notchWidth - this.options.notchSpacing, y); } // calculate widest bar VALUE if (this.options.valueFontSizeAsPct) { var fsize = 50; var widestValue = null; var widestValueWidth = 0; this.ctx.font = this.options.valueFontWeight + ' ' + fsize + 'px ' + this.options.valueFontFamily; for (var _i2 = 0; _i2 < this.data.length; _i2++) { var displayValue = this.data[_i2].displayValue; if (!displayValue) { var totalValue = _lodash2.default.sum(this.data[_i2].segments, function (segment) { return segment.value; }); displayValue = this.data[_i2].prefix + totalValue + this.data[_i2].suffix; } var width = this.ctx.measureText(displayValue).width; if (width > widestValueWidth) { widestValue = displayValue; widestValueWidth = width; break; } } // now we know which value is the widest. Let's iterate until we find a font size that fits the percent provided var optimalValueWidth = this.options.valueFontSizeAsPct * groupWidth; var cont = true; while (cont) { this.ctx.font = this.options.valueFontWeight + ' ' + fsize + 'px ' + this.options.valueFontFamily; var _width = this.ctx.measureText(widestValue).width; if (_width <= optimalValueWidth) { cont = false; } else { fsize = Math.floor(0.9 * fsize); } } this.options.valueFontSize = fsize; } this.renderBars(allowedGroupWidth, groupWidth, allowedBarWidth, barWidth); this.renderLegend(allowedGroupWidth, groupWidth, allowedBarWidth, barWidth); if (this.hits.segment && (this.hits.segment.onClick || this.options.onClick)) { this.htmlCanvas.style.cursor = 'pointer'; } else { this.htmlCanvas.style.cursor = 'default'; } // draw fps if (this.options.DEBUG) { this.ctx.fillStyle = '#666666'; this.ctx.fillRect(canvasWidth - 40, canvasHeight - 15, 40, 15); this.ctx.textAlign = 'right'; this.ctx.textBaseline = 'bottom'; this.ctx.fillStyle = '#000000'; this.ctx.font = '11px sans-serif'; this.ctx.fillText(String(Math.round(this.fps)) + ' fps', canvasWidth - 5, canvasHeight - 1); } this.ctx.restore(); } }, { key: 'renderLegend', value: function renderLegend(allowedGroupWidth, groupWidth) { var _this5 = this; // highlighted bar and show legend (if applicable) this.data.forEach(function (group, i) { if (group === _this5.hits.group) { var groupX = _this5.graphArea.left + i * allowedGroupWidth + (allowedGroupWidth - groupWidth) / 2; // const groupY = (this.plotVars.canvasHeight - this.graphArea.bottom); // some vars for the highlight // value at the top if (_this5.options.showValues) { _this5.ctx.fillStyle = _this5.options.valueFontColor || _this5.options.gridLineColor; _this5.ctx.textAlign = 'center'; _this5.ctx.textBaseline = 'top'; _this5.ctx.font = _this5.options.valueFontWeight + ' ' + _this5.options.valueFontSize + 'px ' + _this5.options.valueFontFamily; var totalDisplayValue = group.displayValue; if (!totalDisplayValue) { var totalValue = _lodash2.default.sum(group.segments, function (segment) { return segment.value; }); totalDisplayValue = group.prefix + totalValue + group.suffix; } _this5.ctx.fillText(totalDisplayValue, groupX + groupWidth / 2, _this5.graphArea.top); } // determine whether the legends should appear on the right or left of the bar _this5.ctx.font = _this5.options.legendFontWeight + ' ' + parseFloat(_this5.options.legendFontSize) + 'px ' + _this5.options.legendFontFamily; // render legend/tooltip for highlight of mouseover (if any) // determine width of tooltip/legend _this5.ctx.font = parseFloat(_this5.options.legendFontSize) + 'px ' + _this5.options.legendFontFamily; _this5.ctx.textBaseline = 'baseline'; _this5.ctx.textAlign = 'left'; var widestValue = 0; for (var s = 0; s < group.segments.length; s++) { var w = _this5.ctx.measureText(group.segments[s].name + ': ' + group.segments[s].prefix + _Numbers2.default.formatNumber(group.segments[s].value) + group.segments[s].suffix).width; if (w > widestValue) { widestValue = w; } } var legendWidth = _this5.options.colorBarWidth + _this5.options.colorBarSpacing + widestValue + _this5.options.tooltipPadding * 2; // determine height of legend/tooltip var legendHeight = group.segments.length * (parseFloat(_this5.options.legendFontSize) * 1.25) + _this5.options.tooltipPadding * 2; // determine where the legend/tooltip will fit var legendX = groupX + groupWidth + _this5.options.barLegendSpacing; if (legendX + legendWidth > _this5.graphArea.left + _this5.graphArea.width) { legendX = groupX - legendWidth - _this5.options.barLegendSpacing; } var legendY = _this5.graphArea.top; // draw legend/tooltip box _this5.ctx.fillStyle = _this5.options.legendBackgroundColor.toString(); _this5.ctx.fillRect(legendX, legendY, legendWidth, legendHeight); // draw four corners _this5.ctx.strokeStyle = _this5.options.legendOutlineColor.toString(); _this5.ctx.lineWidth = _this5.options.legendOutlineWidth; _this5.ctx.beginPath(); _this5.ctx.moveTo(legendX + _this5.options.legendOutlineWidth, legendY + _this5.options.legendOutlineCornerSize); _this5.ctx.lineTo(legendX + _this5.options.legendOutlineWidth, legendY + _this5.options.legendOutlineWidth); _this5.ctx.lineTo(legendX + _this5.options.legendOutlineCornerSize, legendY + _this5.options.legendOutlineWidth); _this5.ctx.moveTo(legendX + legendWidth - _this5.options.legendOutlineCornerSize, legendY + _this5.options.legendOutlineWidth); _this5.ctx.lineTo(legendX + legendWidth - _this5.options.legendOutlineWidth, legendY + _this5.options.legendOutlineWidth); _this5.ctx.lineTo(legendX + legendWidth - _this5.options.legendOutlineWidth, legendY + _this5.options.legendOutlineCornerSize); _this5.ctx.moveTo(legendX + legendWidth - _this5.options.legendOutlineWidth, legendY + legendHeight - _this5.options.legendOutlineCornerSize); _this5.ctx.lineTo(legendX + legendWidth - _this5.options.legendOutlineWidth, legendY + legendHeight - _this5.options.legendOutlineWidth); _this5.ctx.lineTo(legendX + legendWidth - _this5.options.legendOutlineCornerSize, legendY + legendHeight - _this5.options.legendOutlineWidth); _this5.ctx.moveTo(legendX + _this5.options.legendOutlineCornerSize, legendY + legendHeight - _this5.options.legendOutlineWidth); _this5.ctx.lineTo(legendX + _this5.options.legendOutlineWidth, legendY + legendHeight - _this5.options.legendOutlineWidth); _this5.ctx.lineTo(legendX + _this5.options.legendOutlineWidth, legendY + legendHeight - _this5.options.legendOutlineCornerSize); _this5.ctx.stroke(); // draw values for each series (one per line) for (var _s = 0; _s < group.segments.length; _s++) { var lineY = _s * parseFloat(_this5.options.legendFontSize) * 1.25; // draw a color bar for reference _this5.ctx.fillStyle = group.segments[_s].barFillColor; _this5.ctx.fillRect(legendX + _this5.options.tooltipPadding, legendY + _this5.options.tooltipPadding + lineY, _this5.options.colorBarWidth, parseFloat(_this5.options.legendFontSize)); var serieslabelw = _this5.ctx.measureText(group.segments[_s].name + ': ').width; // draw the number/value in the series color _this5.ctx.fillText(group.segments[_s].prefix + _Numbers2.default.formatNumber(group.segments[_s].value) + group.segments[_s].suffix, legendX + _this5.options.tooltipPadding + _this5.options.colorBarWidth + _this5.options.colorBarSpacing + serieslabelw, legendY + _this5.options.tooltipPadding + lineY); // draw the series label in a plain label color _this5.ctx.fillStyle = _this5.options.legendFontColor.toString(); _this5.ctx.fillText(group.segments[_s].name + ': ', legendX + _this5.options.tooltipPadding + _this5.options.colorBarWidth + _this5.options.colorBarSpacing, legendY + _this5.options.tooltipPadding + lineY); } } }); } }, { key: 'renderBars', value: function renderBars(allowedGroupWidth, groupWidth, allowedBarWidth, barWidth) { var _this6 = this; // draw bars this.data.forEach(function (group, i) { var groupX = _this6.graphArea.left + i * allowedGroupWidth + (allowedGroupWidth - groupWidth) / 2; var groupY = _this6.plotVars.canvasHeight - _this6.graphArea.bottom; group.x = groupX; group.y = _this6.graphArea.bottom; group.y1 = groupY; group.x1 = groupX + groupWidth; if (_this6.hits.group && _this6.hits.group === group) { _this6.ctx.fillStyle = new _Color2.default.Color(_this6.options.gridLineColor).fade(0.5); _this6.ctx.fillRect(groupX, groupY - _this6.graphArea.height, groupWidth, _this6.graphArea.height); } _this6.renderSegments(group, allowedBarWidth, barWidth); // bar label if (group.name !== undefined) { _this6.ctx.fillStyle = (_this6.options.barLabelFontColor || _this6.options.gridLineColor).toString(); if (_this6.options.barLabelOrientation === 'horizontal') { _this6.ctx.font = _this6.options.barLabelFontSize + 'px ' + _this6.options.barLabelFontFamily; _this6.ctx.textBaseline = 'top'; _this6.ctx.textAlign = 'center'; _this6.ctx.fillText(group.name, groupX + groupWidth / 2, _this6.plotVars.canvasHeight - _this6.graphArea.bottom + _this6.options.barLabelSpacing); } else { _this6.ctx.font = _this6.options.barLabelFontSize + 'px ' + _this6.options.barLabelFontFamily; _this6.ctx.textBaseline = 'middle'; _this6.ctx.textAlign = 'right'; _this6.ctx.save(); _this6.ctx.translate(groupX + groupWidth / 2, _this6.graphArea.top + _this6.graphArea.height + _this6.options.barLabelSpacing); _this6.ctx.rotate(-Math.PI / 2); _this6.ctx.fillText(group.name, 0, 0); _this6.ctx.restore(); } } }); } }, { key: 'renderSegments', value: function renderSegments(group, allowedBarWidth, barWidth) { var _this7 = this; // draw each segment (group.segments || []).forEach(function (segment, i) { if (segment.value !== undefined) { var barFillColor = new _Color2.default.Color(_this7.options.barFillColor); if (segment.barFillColor) { barFillColor = new _Color2.default.Color(segment.barFillColor); } if (_this7.hits.group === group && _this7.hits.segment && _this7.hits.segment !== segment && (_this7.hits.segment.onClick || _this7.options.onClick)) { barFillColor.fade(0.4); } _this7.ctx.fillStyle = barFillColor.toString(); var barHeight = segment.value / _this7.plotVars.chartMaxY * _this7.graphArea.height; barHeight = Math.max(barHeight, 1); var barX = group.x + i * allowedBarWidth + (allowedBarWidth - barWidth) / 2; var barY = group.y1 - barHeight; segment.x = barX; segment.y = barY; segment.y1 = group.y1; segment.x1 = barX + barWidth; _this7.ctx.fillRect(barX, barY, barWidth, barHeight); // diagonal stripes if (_this7.options.barStripeType !== 'none') { _this7.ctx.save(); _this7.ctx.beginPath(); _this7.ctx.rect(barX, barY, barWidth, barHeight); _this7.ctx.clip(); var barStripeColor = new _Color2.default.Color(_this7.options.barStripeColor); if (segment.barStripeColor) { barStripeColor = new _Color2.default.Color(segment.barStripeColor); } if (_this7.hits.group && group !== _this7.hits.group) { barStripeColor.fade(0.7); } _this7.ctx.strokeStyle = barStripeColor.toString(); _this7.ctx.lineWidth = _this7.options.barStripeWidth; _this7.ctx.beginPath(); switch (_this7.options.barStripeType) { case 'diagonal': for (var _stripeY = barY; _stripeY < barY + barHeight; _stripeY += _this7.options.barStripeSpacing) { _this7.ctx.moveTo(barX, _stripeY + barWidth); _this7.ctx.lineTo(barX + barWidth, _stripeY); } break; case 'horizontal': for (var _stripeY2 = barY; _stripeY2 < barY + barHeight; _stripeY2 += _this7.options.barStripeSpacing) { _this7.ctx.moveTo(barX, _stripeY2); _this7.ctx.lineTo(barX + barWidth, _stripeY2); } break; case 'vertical': var stripeY = barY; for (var m = barX; m < barX + barWidth; m += _this7.options.barStripeSpacing) { _this7.ctx.moveTo(m, stripeY); _this7.ctx.lineTo(m, stripeY + barHeight); } break; default: break; } _this7.ctx.stroke(); _this7.ctx.restore(); } // draw border around bar segments if (segment.barBorderColor || _this7.options.barBorderColor && _this7.options.barBorderColor.toString() !== 'transparent') { _this7.ctx.beginPath(); var barBorderColor = new _Color2.default.Color(_this7.options.barBorderColor); if (segment.barBorderColor) { barBorderColor = new _Color2.default.Color(segment.barBorderColor); } if (_this7.hits.group && _this7.hits.group !== group) { barBorderColor.fade(0.7); } _this7.ctx.strokeStyle = barBorderColor.toString(); _this7.ctx.rect(barX, barY, barWidth, barHeight); _this7.ctx.stroke(); } } }); } }, { key: 'unhighlightAll', value: function unhighlightAll() { this.hits = {}; } }, { key: 'getHitsFromPoint', value: function getHitsFromPoint(pt) { var _this8 = this; var hits = { segment: false, group: false }; hits.group = this.data.find(function (group) { hits.segment = group.segments.find(function (segment) { // find to get it to stop appropriately return _this8.pointWithinSegment(pt, segment); }); if (hits.segment) { return true; } else { return _this8.pointWithinSegment(pt, group); } }); return hits; } }, { key: 'pointWithinSegment', value: function pointWithinSegment(pt, segment) { return pt.x >= segment.x && pt.x <= segment.x1 && pt.y >= segment.y && pt.y <= segment.y1; } }]); return GroupedBarGraph; }(); exports.default = GroupedBarGraph;