UNPKG

transcend-charts

Version:

Transcend is a charting and graph library for NUVI

501 lines (463 loc) 18.7 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 _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 HorizontalBarList = function () { function HorizontalBarList(el, graphData, options) { var _this = this; _classCallCheck(this, HorizontalBarList); this.lastFrame = new Date().getTime(); this.isDestroyed = false; this.el = el; this.highlightedIndex = -1; this.autoBind(); this.setData(graphData); this.setOptions(options); this.build(); this.buildPlotInfo(); this.attach(); this.requestAnimationFrame(); window.setTimeout(function () { _this.fillParent(); }, 0); } _createClass(HorizontalBarList, [{ key: 'build', value: function build() { this.htmlCanvas = document.createElement('CANVAS'); this.el.appendChild(this.htmlCanvas); } }, { 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: '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); } }, { key: 'isPercentage', value: function isPercentage(x) { var str = String(x); return str.substr(-1) === '%'; } }, { key: 'setData', value: function setData(someData) { this.needsRender = true; if (someData && someData.length) { this.data = someData.map(function (datum) { var parts = _Numbers2.default.separateNumberUnits(datum.value); if (datum.barFillColor) { parts.barFillColor = new _Color2.default.Color(datum.barFillColor); } if (datum.barBorderColor) { parts.barBorderColor = new _Color2.default.Color(datum.barBorderColor); } if (datum.barStripeColor) { parts.barStripeColor = new _Color2.default.Color(datum.barStripeColor); } return Object.assign({}, parts, datum, { displayValue: parts.value }); }); } else { this.data = []; } } }, { key: 'setOptions', value: function setOptions(options) { this.options = Object.assign({}, { backgroundColor: 'transparent', barBorderColor: 'transparent', barBackgroundColor: '#909090', barFillColor: '#999', barLabelFontColor: false, barLabelFontFamily: 'Arial', barLabelFontSize: 12, barLabelPosition: 'above', barLabelSpacing: 10, barSpacing: 1, barStripeColor: '#666666', barStripeSpacing: 10, barStripeType: 'none', barStripeWidth: 2, barValueSpacing: 10, gridFontColor: false, highlightedBarFillColor: '#999', highlightedBarLabelFontColor: '#FFFFFF', highlightedValueFontColor: '#FFFFFF', padding: 0, showValues: true, valueFontColor: false, valueFontFamily: 'Arial', valueFontSize: 14, valueFontWeight: '400', maxBarHeight: -1 }, options); this.colorizeOptions(); } }, { key: 'colorizeOptions', value: function colorizeOptions() { var _this2 = this; var keys = ['backgroundColor', 'barBackgroundColor', 'barBorderColor', 'barFillColor', 'barLabelFontColor', 'barStripeColor', 'gridFontColor', 'highlightedBarFillColor', 'highlightedBarLabelFontColor', 'highlightedValueFontColor', 'valueFontColor']; keys.forEach(function (key) { var color = _this2.options[key]; if (!color) { color = '#666'; } _this2.options[key] = new _Color2.default.Color(color); }); } }, { key: 'buildPlotInfo', value: function buildPlotInfo() { this.plotInfo = { graphArea: { top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0 } }; } }, { key: 'buildFontSize', value: function buildFontSize(fontSizeString, relativeTo) { var fontSize = fontSizeString; if (this.isPercentage(fontSizeString)) { fontSize = parseFloat(fontSize.substr(0, fontSize.length - 1)) / 100; fontSize = String(fontSize * relativeTo); } fontSize = String(fontSize); if (fontSize.substr(-2).toLowerCase() !== 'px') { fontSize = fontSize + 'px'; } return fontSize; } }, { key: 'onWindowResize', value: function onWindowResize() { this.fillParent(); } }, { key: 'render', value: function render(newData) { if (newData) { this.setData(newData); } if (!this.data) { return false; } var ctx = this.htmlCanvas.getContext('2d'); var pxRatio = window.devicePixelRatio || 1; var options = this.options; var data = this.data; if (this.highlightedIndex > -1) { this.htmlCanvas.style.cursor = 'pointer'; } else { this.htmlCanvas.style.cursor = 'default'; } // 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 = this.htmlCanvas.width / (pxRatio / backingStoreRatio); var canvasHeight = this.htmlCanvas.height / (pxRatio / backingStoreRatio); // fill background ctx.clearRect(0, 0, canvasWidth, canvasHeight); if (options.backgroundColor.getRgba() !== 'transparent') { ctx.fillStyle = options.backgroundColor.getRgba(); ctx.fillRect(0, 0, canvasWidth, canvasHeight); } // calculate important measurements this.plotInfo.maxY = _lodash2.default.max(data, function (dataPoint) { return dataPoint.value; }).value; this.plotInfo.minY = 0; // calculate grid area this.plotInfo.graphArea = { left: options.padding, top: options.padding, right: options.padding, bottom: options.padding }; this.plotInfo.graphArea.width = canvasWidth - this.plotInfo.graphArea.left - this.plotInfo.graphArea.right; this.plotInfo.graphArea.height = canvasHeight - this.plotInfo.graphArea.top - this.plotInfo.graphArea.bottom; this.plotInfo.chartMaxY = this.plotInfo.maxY; var allowedBarHeight = this.plotInfo.graphArea.height / data.length - options.barSpacing; var barHeight = allowedBarHeight; if (this.options.maxBarHeight > 0) { barHeight = Math.min(barHeight, this.options.maxBarHeight); } var barHeightWithSpacing = barHeight + options.barSpacing; // calculate the widest label ctx.font = this.buildFontSize(options.barLabelFontSize, barHeight) + ' ' + options.barLabelFontFamily; var datumWithWidestLabel = _lodash2.default.max(data, function (datum) { return ctx.measureText(datum.name).width; }); this.plotInfo.widestLabelWidth = ctx.measureText(datumWithWidestLabel.name).width; // draw bars for (var i = 0; i < data.length; i++) { var y = this.plotInfo.graphArea.top + i * barHeightWithSpacing; ctx.fillStyle = options.barBackgroundColor.getRgba(); ctx.fillRect(this.plotInfo.graphArea.left, y, this.plotInfo.graphArea.width, barHeight); // bar if (data[i].displayValue !== undefined) { if (this.highlightedIndex === i) { ctx.fillStyle = options.highlightedBarFillColor.getRgba(); } else if (data[i].barFillColor) { ctx.fillStyle = data[i].barFillColor.getRgba(); } else { ctx.fillStyle = options.barFillColor.getRgba(); } var barWidth = data[i].displayValue / this.plotInfo.chartMaxY * this.plotInfo.graphArea.width; ctx.fillRect(this.plotInfo.graphArea.left, y, barWidth, barHeight); data[i].barWidth = barWidth; data[i].y = y; data[i].barHeight = barHeight; // diagonal stripes if (options.barStripeType !== 'none') { ctx.save(); ctx.rect(this.plotInfo.graphArea.left, y, barWidth, barHeight); ctx.clip(); if (data[i].barStripeColor) { ctx.strokeStyle = data[i].barStripeColor.getRgba(); } else { ctx.strokeStyle = options.barStripeColor.getRgba(); } ctx.lineWidth = options.barStripeWidth; ctx.beginPath(); switch (options.barStripeType) { case 'diagonal': for (var x = this.plotInfo.graphArea.left - barHeight; x < this.plotInfo.graphArea.left + barWidth; x += options.barStripeSpacing) { ctx.moveTo(x, y); ctx.lineTo(x + barHeight, y + barHeight); } break; case 'horizontal': for (var m = y; m < y + barHeight; m += options.barStripeSpacing) { ctx.moveTo(this.plotInfo.graphArea.left, m); ctx.lineTo(canvasWidth - this.plotInfo.graphArea.right, m); } break; case 'vertical': default: for (var _x = this.plotInfo.graphArea.left; _x < this.plotInfo.graphArea.left + barWidth; _x += options.barStripeSpacing) { ctx.moveTo(_x, y); ctx.lineTo(_x, y + barHeight); } break; } ctx.stroke(); ctx.restore(); } if (data[i].barBorderColor || options.barBorderColor.getRgba() !== 'transparent') { ctx.beginPath(); if (data[i].barBorderColor) { ctx.strokeStyle = data[i].barBorderColor.getRgba(); } else { ctx.strokeStyle = options.barBorderColor.getRgba(); } ctx.rect(this.plotInfo.graphArea.left, y, barWidth, barHeight); ctx.stroke(); } } // bar label if (data[i].name !== undefined) { if (this.highlightedIndex === i) { ctx.fillStyle = options.highlightedBarLabelFontColor.getRgba(); } else { ctx.fillStyle = options.barLabelFontColor.getRgba(); } var barLabelFontSize = this.buildFontSize(options.barLabelFontSize, barHeight); ctx.font = barLabelFontSize + ' ' + options.barLabelFontFamily; ctx.textBaseline = 'middle'; ctx.textAlign = 'left'; ctx.fillText(data[i].name, this.plotInfo.graphArea.left + options.barLabelSpacing, y + barHeight / 2); } // bar value if (options.showValues) { var displayValue = data[i].prefix + data[i].value + data[i].suffix; if (data[i].displayValue) { displayValue = data[i].displayValue; } if (this.highlightedIndex === i) { ctx.fillStyle = options.highlightedValueFontColor.getRgba(); } else { ctx.fillStyle = options.valueFontColor.getRgba(); } var valueFontSize = this.buildFontSize(options.valueFontSize, barHeight); ctx.font = valueFontSize + ' ' + options.valueFontFamily; ctx.fillText(displayValue, this.plotInfo.graphArea.left + options.barLabelSpacing + this.plotInfo.widestLabelWidth + options.barValueSpacing, y + barHeight / 2); } } // draw fps if (this.options.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(this.fps)) + ' fps', canvasWidth - 5, canvasHeight - 1); } ctx.restore(); } }, { 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: 'onAnimateFrame', value: function onAnimateFrame() { var thisFrame = new Date().getTime(); var elapsed = thisFrame - this.lastFrame; // elapsed time since last render var fps = 1000 / elapsed; if (this.needsRender) { this.needsRender = false; this.fps = fps; this.render(); this.lastFrame = thisFrame; } if (!this.isDestroyed) { this.requestAnimationFrame(); } } }, { key: 'requestAnimationFrame', value: function requestAnimationFrame() { 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: 'hitTest', value: function hitTest(coords) { if (this.plotInfo && this.plotInfo.graphArea) { var item = this.data.find(function (datum) { return datum.y <= coords.y && datum.y + datum.barHeight >= coords.y; }); return this.data.indexOf(item); } return -1; } }, { key: 'onFullscreenChange', value: function onFullscreenChange(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] === this.htmlCanvas) { canvases[c].width = 1; canvases[c].height = 1; canvases[c].style.width = '1px'; canvases[c].style.height = '1px'; break; } } } } }, { key: 'onClick', value: function onClick() { if (this.options.onClick && this.highlightedIndex > -1) { var item = this.data[this.highlightedIndex]; this.options.onClick(item); } } }, { key: 'onMouseMove', value: function onMouseMove(evt) { if (this.options.onClick) { var rect = this.htmlCanvas.getBoundingClientRect(); var coords = { x: evt.clientX - rect.left, y: evt.clientY - rect.top }; this.highlightedIndex = this.hitTest(coords); this.needsRender = true; } } }, { key: 'onMouseOut', value: function onMouseOut() { if (this.highlightedIndex > -1) { this.highlightedIndex = -1; this.needsRender = true; } } }]); return HorizontalBarList; }(); exports.default = HorizontalBarList;