UNPKG

@qogni/dygraphs

Version:

dygraphs is a fast, flexible open source JavaScript charting library.

323 lines (276 loc) 10.2 kB
/** * @license * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) * MIT-licensed (http://opensource.org/licenses/MIT) */ /*global Dygraph:false */ 'use strict'; /* Bits of jankiness: - Direct layout access - Direct area access - Should include calculation of ticks, not just the drawing. Options left to make axis-friendly. ('drawAxesAtZero') ('xAxisHeight') */ Object.defineProperty(exports, '__esModule', { value: true }); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } var _dygraphUtils = require('../dygraph-utils'); var utils = _interopRequireWildcard(_dygraphUtils); /** * Draws the axes. This includes the labels on the x- and y-axes, as well * as the tick marks on the axes. * It does _not_ draw the grid lines which span the entire chart. */ var axes = function axes() { this.xlabels_ = []; this.ylabels_ = []; }; axes.prototype.toString = function () { return 'Axes Plugin'; }; axes.prototype.activate = function (g) { return { layout: this.layout, clearChart: this.clearChart, willDrawChart: this.willDrawChart }; }; axes.prototype.layout = function (e) { var g = e.dygraph; if (g.getOptionForAxis('drawAxis', 'y')) { var w = g.getOptionForAxis('axisLabelWidth', 'y') + 2 * g.getOptionForAxis('axisTickSize', 'y'); e.reserveSpaceLeft(w); } if (g.getOptionForAxis('drawAxis', 'x')) { var h; // NOTE: I think this is probably broken now, since g.getOption() now // hits the dictionary. (That is, g.getOption('xAxisHeight') now always // has a value.) if (g.getOption('xAxisHeight')) { h = g.getOption('xAxisHeight'); } else { h = g.getOptionForAxis('axisLabelFontSize', 'x') + 2 * g.getOptionForAxis('axisTickSize', 'x'); } e.reserveSpaceBottom(h); } if (g.numAxes() == 2) { if (g.getOptionForAxis('drawAxis', 'y2')) { var w = g.getOptionForAxis('axisLabelWidth', 'y2') + 2 * g.getOptionForAxis('axisTickSize', 'y2'); e.reserveSpaceRight(w); } } else if (g.numAxes() > 2) { g.error('Only two y-axes are supported at this time. (Trying ' + 'to use ' + g.numAxes() + ')'); } }; axes.prototype.detachLabels = function () { function removeArray(ary) { for (var i = 0; i < ary.length; i++) { var el = ary[i]; if (el.parentNode) el.parentNode.removeChild(el); } } removeArray(this.xlabels_); removeArray(this.ylabels_); this.xlabels_ = []; this.ylabels_ = []; }; axes.prototype.clearChart = function (e) { this.detachLabels(); }; axes.prototype.willDrawChart = function (e) { var _this = this; var g = e.dygraph; if (!g.getOptionForAxis('drawAxis', 'x') && !g.getOptionForAxis('drawAxis', 'y') && !g.getOptionForAxis('drawAxis', 'y2')) { return; } // Round pixels to half-integer boundaries for crisper drawing. function halfUp(x) { return Math.round(x) + 0.5; } function halfDown(y) { return Math.round(y) - 0.5; } var context = e.drawingContext; var containerDiv = e.canvas.parentNode; var canvasWidth = g.width_; // e.canvas.width is affected by pixel ratio. var canvasHeight = g.height_; var label, x, y, tick, i; var makeLabelStyle = function makeLabelStyle(axis) { return { position: 'absolute', fontSize: g.getOptionForAxis('axisLabelFontSize', axis) + 'px', width: g.getOptionForAxis('axisLabelWidth', axis) + 'px' }; }; var labelStyles = { x: makeLabelStyle('x'), y: makeLabelStyle('y'), y2: makeLabelStyle('y2') }; var makeDiv = function makeDiv(txt, axis, prec_axis) { /* * This seems to be called with the following three sets of axis/prec_axis: * x: undefined * y: y1 * y: y2 */ var div = document.createElement('div'); var labelStyle = labelStyles[prec_axis == 'y2' ? 'y2' : axis]; utils.update(div.style, labelStyle); // TODO: combine outer & inner divs var inner_div = document.createElement('div'); inner_div.className = 'dygraph-axis-label' + ' dygraph-axis-label-' + axis + (prec_axis ? ' dygraph-axis-label-' + prec_axis : ''); inner_div.innerHTML = txt; div.appendChild(inner_div); return div; }; // axis lines context.save(); var layout = g.layout_; var area = e.dygraph.plotter_.area; // Helper for repeated axis-option accesses. var makeOptionGetter = function makeOptionGetter(axis) { return function (option) { return g.getOptionForAxis(option, axis); }; }; if (g.getOptionForAxis('drawAxis', 'y')) { if (layout.yticks && layout.yticks.length > 0) { var num_axes = g.numAxes(); var getOptions = [makeOptionGetter('y'), makeOptionGetter('y2')]; layout.yticks.forEach(function (tick) { if (tick.label === undefined) return; // this tick only has a grid line. x = area.x; var sgn = 1; var prec_axis = 'y1'; var getAxisOption = getOptions[0]; if (tick.axis == 1) { // right-side y-axis x = area.x + area.w; sgn = -1; prec_axis = 'y2'; getAxisOption = getOptions[1]; } var fontSize = getAxisOption('axisLabelFontSize'); y = area.y + tick.pos * area.h; /* Tick marks are currently clipped, so don't bother drawing them. context.beginPath(); context.moveTo(halfUp(x), halfDown(y)); context.lineTo(halfUp(x - sgn * this.attr_('axisTickSize')), halfDown(y)); context.closePath(); context.stroke(); */ label = makeDiv(tick.label, 'y', num_axes == 2 ? prec_axis : null); var top = y - fontSize / 2; if (top < 0) top = 0; if (top + fontSize + 3 > canvasHeight) { label.style.bottom = '0'; } else { label.style.top = top + 'px'; } // TODO: replace these with css classes? if (tick.axis === 0) { label.style.left = area.x - getAxisOption('axisLabelWidth') - getAxisOption('axisTickSize') + 'px'; label.style.textAlign = 'right'; } else if (tick.axis == 1) { label.style.left = area.x + area.w + getAxisOption('axisTickSize') + 'px'; label.style.textAlign = 'left'; } label.style.width = getAxisOption('axisLabelWidth') + 'px'; containerDiv.appendChild(label); _this.ylabels_.push(label); }); // The lowest tick on the y-axis often overlaps with the leftmost // tick on the x-axis. Shift the bottom tick up a little bit to // compensate if necessary. var bottomTick = this.ylabels_[0]; // Interested in the y2 axis also? var fontSize = g.getOptionForAxis('axisLabelFontSize', 'y'); var bottom = parseInt(bottomTick.style.top, 10) + fontSize; if (bottom > canvasHeight - fontSize) { bottomTick.style.top = parseInt(bottomTick.style.top, 10) - fontSize / 2 + 'px'; } } // draw a vertical line on the left to separate the chart from the labels. var axisX; if (g.getOption('drawAxesAtZero')) { var r = g.toPercentXCoord(0); if (r > 1 || r < 0 || isNaN(r)) r = 0; axisX = halfUp(area.x + r * area.w); } else { axisX = halfUp(area.x); } context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y'); context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y'); context.beginPath(); context.moveTo(axisX, halfDown(area.y)); context.lineTo(axisX, halfDown(area.y + area.h)); context.closePath(); context.stroke(); // if there's a secondary y-axis, draw a vertical line for that, too. if (g.numAxes() == 2) { context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y2'); context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y2'); context.beginPath(); context.moveTo(halfDown(area.x + area.w), halfDown(area.y)); context.lineTo(halfDown(area.x + area.w), halfDown(area.y + area.h)); context.closePath(); context.stroke(); } } if (g.getOptionForAxis('drawAxis', 'x')) { if (layout.xticks) { var getAxisOption = makeOptionGetter('x'); layout.xticks.forEach(function (tick) { if (tick.label === undefined) return; // this tick only has a grid line. x = area.x + tick.pos * area.w; y = area.y + area.h; /* Tick marks are currently clipped, so don't bother drawing them. context.beginPath(); context.moveTo(halfUp(x), halfDown(y)); context.lineTo(halfUp(x), halfDown(y + this.attr_('axisTickSize'))); context.closePath(); context.stroke(); */ label = makeDiv(tick.label, 'x'); label.style.textAlign = 'center'; label.style.top = y + getAxisOption('axisTickSize') + 'px'; var left = x - getAxisOption('axisLabelWidth') / 2; if (left + getAxisOption('axisLabelWidth') > canvasWidth) { left = canvasWidth - getAxisOption('axisLabelWidth'); label.style.textAlign = 'right'; } if (left < 0) { left = 0; label.style.textAlign = 'left'; } label.style.left = left + 'px'; label.style.width = getAxisOption('axisLabelWidth') + 'px'; containerDiv.appendChild(label); _this.xlabels_.push(label); }); } context.strokeStyle = g.getOptionForAxis('axisLineColor', 'x'); context.lineWidth = g.getOptionForAxis('axisLineWidth', 'x'); context.beginPath(); var axisY; if (g.getOption('drawAxesAtZero')) { var r = g.toPercentYCoord(0, 0); if (r > 1 || r < 0) r = 1; axisY = halfDown(area.y + r * area.h); } else { axisY = halfDown(area.y + area.h); } context.moveTo(halfUp(area.x), axisY); context.lineTo(halfUp(area.x + area.w), axisY); context.closePath(); context.stroke(); } context.restore(); }; exports['default'] = axes; module.exports = exports['default'];