1 //TODO: consider deprecating by adding necessary features to multiBar model
2 nv.models.discreteBar = function() {
4 //============================================================
5 // Public Variables with Default Settings
6 //------------------------------------------------------------
8 var margin = {top: 0, right: 0, bottom: 0, left: 0}
11 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
12 , x = d3.scale.ordinal()
13 , y = d3.scale.linear()
14 , getX = function(d) { return d.x }
15 , getY = function(d) { return d.y }
16 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
17 , color = nv.utils.defaultColor()
19 , valueFormat = d3.format(',.2f')
24 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
25 , rectClass = 'discreteBar'
28 //============================================================
31 //============================================================
33 //------------------------------------------------------------
37 //============================================================
40 function chart(selection) {
41 selection.each(function(data) {
42 var availableWidth = width - margin.left - margin.right,
43 availableHeight = height - margin.top - margin.bottom,
44 container = d3.select(this);
47 //add series index to each data point for reference
48 data.forEach(function(series, i) {
49 series.values.forEach(function(point) {
55 //------------------------------------------------------------
58 // remap and flatten the data for use in calculating the scales' domains
59 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
60 data.map(function(d) {
61 return d.values.map(function(d,i) {
62 return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
66 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
67 .rangeBands(xRange || [0, availableWidth], .1);
69 y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
72 // If showValues, pad the Y axis range to account for label height
73 if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
74 else y.range(yRange || [availableHeight, 0]);
76 //store old scales if they exist
78 y0 = y0 || y.copy().range([y(0),y(0)]);
80 //------------------------------------------------------------
83 //------------------------------------------------------------
84 // Setup containers and skeleton of chart
86 var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
87 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
88 var gEnter = wrapEnter.append('g');
89 var g = wrap.select('g');
91 gEnter.append('g').attr('class', 'nv-groups');
93 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
95 //------------------------------------------------------------
99 //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
100 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
101 .data(function(d) { return d }, function(d) { return d.key });
102 groups.enter().append('g')
103 .style('stroke-opacity', 1e-6)
104 .style('fill-opacity', 1e-6);
107 .style('stroke-opacity', 1e-6)
108 .style('fill-opacity', 1e-6)
111 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
112 .classed('hover', function(d) { return d.hover });
115 .style('stroke-opacity', 1)
116 .style('fill-opacity', .75);
119 var bars = groups.selectAll('g.nv-bar')
120 .data(function(d) { return d.values });
122 bars.exit().remove();
125 var barsEnter = bars.enter().append('g')
126 .attr('transform', function(d,i,j) {
127 return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
129 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
130 d3.select(this).classed('hover', true);
131 dispatch.elementMouseover({
134 series: data[d.series],
135 pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
137 seriesIndex: d.series,
141 .on('mouseout', function(d,i) {
142 d3.select(this).classed('hover', false);
143 dispatch.elementMouseout({
146 series: data[d.series],
148 seriesIndex: d.series,
152 .on('click', function(d,i) {
153 dispatch.elementClick({
156 series: data[d.series],
157 pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
159 seriesIndex: d.series,
162 d3.event.stopPropagation();
164 .on('dblclick', function(d,i) {
165 dispatch.elementDblClick({
168 series: data[d.series],
169 pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
171 seriesIndex: d.series,
174 d3.event.stopPropagation();
177 barsEnter.append('rect')
179 .attr('width', x.rangeBand() * .9 / data.length )
182 barsEnter.append('text')
183 .attr('text-anchor', 'middle')
187 .text(function(d,i) { return valueFormat(getY(d,i)) })
189 .attr('x', x.rangeBand() * .9 / 2)
190 .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
194 bars.selectAll('text').remove();
198 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
199 .style('fill', function(d,i) { return d.color || color(d,i) })
200 .style('stroke', function(d,i) { return d.color || color(d,i) })
202 .attr('class', rectClass)
204 .attr('width', x.rangeBand() * .9 / data.length);
206 //.delay(function(d,i) { return i * 1200 / data[0].values.length })
207 .attr('transform', function(d,i) {
208 var left = x(getX(d,i)) + x.rangeBand() * .05,
209 top = getY(d,i) < 0 ?
211 y(0) - y(getY(d,i)) < 1 ?
212 y(0) - 1 : //make 1 px positive bars show up above y=0
215 return 'translate(' + left + ', ' + top + ')'
218 .attr('height', function(d,i) {
219 return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
223 //store old scales for use in transitions on update
233 //============================================================
234 // Expose Public Variables
235 //------------------------------------------------------------
237 chart.dispatch = dispatch;
239 chart.options = nv.utils.optionsFunc.bind(chart);
241 chart.x = function(_) {
242 if (!arguments.length) return getX;
247 chart.y = function(_) {
248 if (!arguments.length) return getY;
253 chart.margin = function(_) {
254 if (!arguments.length) return margin;
255 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
256 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
257 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
258 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
262 chart.width = function(_) {
263 if (!arguments.length) return width;
268 chart.height = function(_) {
269 if (!arguments.length) return height;
274 chart.xScale = function(_) {
275 if (!arguments.length) return x;
280 chart.yScale = function(_) {
281 if (!arguments.length) return y;
286 chart.xDomain = function(_) {
287 if (!arguments.length) return xDomain;
292 chart.yDomain = function(_) {
293 if (!arguments.length) return yDomain;
298 chart.xRange = function(_) {
299 if (!arguments.length) return xRange;
304 chart.yRange = function(_) {
305 if (!arguments.length) return yRange;
310 chart.forceY = function(_) {
311 if (!arguments.length) return forceY;
316 chart.color = function(_) {
317 if (!arguments.length) return color;
318 color = nv.utils.getColor(_);
322 chart.id = function(_) {
323 if (!arguments.length) return id;
328 chart.showValues = function(_) {
329 if (!arguments.length) return showValues;
334 chart.valueFormat= function(_) {
335 if (!arguments.length) return valueFormat;
340 chart.rectClass= function(_) {
341 if (!arguments.length) return rectClass;
345 //============================================================