transcend-charts
Version:
Transcend is a charting and graph library for NUVI
501 lines (463 loc) • 18.7 kB
JavaScript
'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;