UNPKG

nvd3-fork

Version:

FORK! of NVD3, a reusable charting library written in d3.js

217 lines (172 loc) 8.58 kB
// Chart design based on the recommendations of Stephen Few. Implementation // based on the work of Clint Ivy, Jamie Love, and Jason Davies. // http://projects.instantcognition.com/protovis/bulletchart/ nv.models.bulletChart = function() { "use strict"; //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ var bullet = nv.models.bullet(); var tooltip = nv.models.tooltip(); var orient = 'left' // TODO top & bottom , reverse = false , margin = {top: 5, right: 40, bottom: 20, left: 120} , ranges = function(d) { return d.ranges } , markers = function(d) { return d.markers ? d.markers : [] } , measures = function(d) { return d.measures } , width = null , height = 55 , tickFormat = null , ticks = null , noData = null , dispatch = d3.dispatch() ; tooltip .duration(0) .headerEnabled(false); function chart(selection) { selection.each(function(d, i) { var container = d3.select(this); nv.utils.initSVG(container); var availableWidth = nv.utils.availableWidth(width, container, margin), availableHeight = height - margin.top - margin.bottom, that = this; chart.update = function() { chart(selection) }; chart.container = this; // Display No Data message if there's nothing to show. if (!d || !ranges.call(this, d, i)) { nv.utils.noData(chart, container) return chart; } else { container.selectAll('.nv-noData').remove(); } var rangez = ranges.call(this, d, i).slice().sort(d3.descending), markerz = markers.call(this, d, i).slice().sort(d3.descending), measurez = measures.call(this, d, i).slice().sort(d3.descending); // Setup containers and skeleton of chart var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]); var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart'); var gEnter = wrapEnter.append('g'); var g = wrap.select('g'); gEnter.append('g').attr('class', 'nv-bulletWrap'); gEnter.append('g').attr('class', 'nv-titles'); wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); // Compute the new x-scale. var x1 = d3.scale.linear() .domain([0, Math.max(rangez[0], (markerz[0] || 0), measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain .range(reverse ? [availableWidth, 0] : [0, availableWidth]); // Retrieve the old x-scale, if this is an update. var x0 = this.__chart__ || d3.scale.linear() .domain([0, Infinity]) .range(x1.range()); // Stash the new scale. this.__chart__ = x1; var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; var title = gEnter.select('.nv-titles').append('g') .attr('text-anchor', 'end') .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')'); title.append('text') .attr('class', 'nv-title') .text(function(d) { return d.title; }); title.append('text') .attr('class', 'nv-subtitle') .attr('dy', '1em') .text(function(d) { return d.subtitle; }); bullet .width(availableWidth) .height(availableHeight); var bulletWrap = g.select('.nv-bulletWrap'); d3.transition(bulletWrap).call(bullet); // Compute the tick format. var format = tickFormat || x1.tickFormat( availableWidth / 100 ); // Update the tick groups. var tick = g.selectAll('g.nv-tick') .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) { return this.textContent || format(d); }); // Initialize the ticks with the old scale, x0. var tickEnter = tick.enter().append('g') .attr('class', 'nv-tick') .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' }) .style('opacity', 1e-6); tickEnter.append('line') .attr('y1', availableHeight) .attr('y2', availableHeight * 7 / 6); tickEnter.append('text') .attr('text-anchor', 'middle') .attr('dy', '1em') .attr('y', availableHeight * 7 / 6) .text(format); // Transition the updating ticks to the new scale, x1. var tickUpdate = d3.transition(tick) .transition() .duration(bullet.duration()) .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) .style('opacity', 1); tickUpdate.select('line') .attr('y1', availableHeight) .attr('y2', availableHeight * 7 / 6); tickUpdate.select('text') .attr('y', availableHeight * 7 / 6); // Transition the exiting ticks to the new scale, x1. d3.transition(tick.exit()) .transition() .duration(bullet.duration()) .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) .style('opacity', 1e-6) .remove(); }); d3.timer.flush(); return chart; } //============================================================ // Event Handling/Dispatching (out of chart's scope) //------------------------------------------------------------ bullet.dispatch.on('elementMouseover.tooltip', function(evt) { evt['series'] = { key: evt.label, value: evt.value, color: evt.color }; tooltip.data(evt).hidden(false); }); bullet.dispatch.on('elementMouseout.tooltip', function(evt) { tooltip.hidden(true); }); bullet.dispatch.on('elementMousemove.tooltip', function(evt) { tooltip(); }); //============================================================ // Expose Public Variables //------------------------------------------------------------ chart.bullet = bullet; chart.dispatch = dispatch; chart.tooltip = tooltip; chart.options = nv.utils.optionsFunc.bind(chart); chart._options = Object.create({}, { // simple options, just get/set the necessary values ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good) markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal) measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast) width: {get: function(){return width;}, set: function(_){width=_;}}, height: {get: function(){return height;}, set: function(_){height=_;}}, tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}}, ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}}, noData: {get: function(){return noData;}, set: function(_){noData=_;}}, // options that require extra logic in the setter margin: {get: function(){return margin;}, set: function(_){ margin.top = _.top !== undefined ? _.top : margin.top; margin.right = _.right !== undefined ? _.right : margin.right; margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; margin.left = _.left !== undefined ? _.left : margin.left; }}, orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom orient = _; reverse = orient == 'right' || orient == 'bottom'; }} }); nv.utils.inheritOptions(chart, bullet); nv.utils.initOptions(chart); return chart; };