1 nv.models.axis = function() {
3 //============================================================
4 // Public Variables with Default Settings
5 //------------------------------------------------------------
7 var axis = d3.svg.axis()
10 var margin = {top: 0, right: 0, bottom: 0, left: 0}
11 , width = 75 //only used for tickLabel currently
12 , height = 60 //only used for tickLabel currently
13 , scale = d3.scale.linear()
14 , axisLabelText = null
15 , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
16 , highlightZero = true
19 , staggerLabels = false
23 , axisLabelDistance = 12 //The larger this number is, the closer the axis label is to the axis.
29 .tickFormat(function(d) { return d })
32 //============================================================
35 //============================================================
37 //------------------------------------------------------------
41 //============================================================
44 function chart(selection) {
45 selection.each(function(data) {
46 var container = d3.select(this);
49 //------------------------------------------------------------
50 // Setup containers and skeleton of chart
52 var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
53 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
54 var gEnter = wrapEnter.append('g');
55 var g = wrap.select('g')
57 //------------------------------------------------------------
62 else if (axis.orient() == 'top' || axis.orient() == 'bottom')
63 axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
66 //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
69 g.transition().call(axis);
71 scale0 = scale0 || axis.scale();
73 var fmt = axis.tickFormat();
75 fmt = scale0.tickFormat();
78 var axisLabel = g.selectAll('text.nv-axislabel')
79 .data([axisLabelText || null]);
80 axisLabel.exit().remove();
81 switch (axis.orient()) {
83 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
84 var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
86 .attr('text-anchor', 'middle')
90 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
91 .data(scale.domain());
92 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
93 axisMaxMin.exit().remove();
95 .attr('transform', function(d,i) {
96 return 'translate(' + scale(d) + ',0)'
100 .attr('y', -axis.tickPadding())
101 .attr('text-anchor', 'middle')
102 .text(function(d,i) {
108 //fmt = d3.format(',.2f');
112 return ('' + v).match('NaN') ? '' : v;
114 axisMaxMin.transition()
115 .attr('transform', function(d,i) {
116 return 'translate(' + scale.range()[i] + ',0)'
121 var xLabelMargin = 36;
122 var maxTextWidth = 30;
123 var xTicks = g.selectAll('g').select("text");
124 if (rotateLabels%360) {
125 //Calculate the longest xTick width
126 xTicks.each(function(d,i){
127 var width = this.getBBox().width;
128 if(width > maxTextWidth) maxTextWidth = width;
130 //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
131 var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
132 var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
135 .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
136 .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
138 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
139 var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
141 .attr('text-anchor', 'middle')
142 .attr('y', xLabelMargin)
145 //if (showMaxMin && !isOrdinal) {
146 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
147 //.data(scale.domain())
148 .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
149 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
150 axisMaxMin.exit().remove();
152 .attr('transform', function(d,i) {
153 return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
157 .attr('y', axis.tickPadding())
158 .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
159 .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
160 .text(function(d,i) {
166 //fmt = d3.format(',.2f');
170 return ('' + v).match('NaN') ? '' : v;
172 axisMaxMin.transition()
173 .attr('transform', function(d,i) {
174 //return 'translate(' + scale.range()[i] + ',0)'
175 //return 'translate(' + scale(d) + ',0)'
176 return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
181 .attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' });
185 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
187 .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
188 .attr('transform', rotateYLabel ? 'rotate(90)' : '')
189 .attr('y', rotateYLabel ? (-Math.max(margin.right,width) + 12) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
190 .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding());
192 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
193 .data(scale.domain());
194 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
195 .style('opacity', 0);
196 axisMaxMin.exit().remove();
198 .attr('transform', function(d,i) {
199 return 'translate(0,' + scale(d) + ')'
204 .attr('x', axis.tickPadding())
205 .style('text-anchor', 'start')
206 .text(function(d,i) {
212 //fmt = d3.format(',.2f');
216 return ('' + v).match('NaN') ? '' : v;
218 axisMaxMin.transition()
219 .attr('transform', function(d,i) {
220 return 'translate(0,' + scale.range()[i] + ')'
223 .style('opacity', 1);
228 //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
229 var yTicks = g.selectAll('g').select("text");
230 yTicks.each(function(d,i){
231 var labelPadding = this.getBBox().width + axis.tickPadding() + 16;
232 if(labelPadding > width) width = labelPadding;
235 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
237 .style('text-anchor', rotateYLabel ? 'middle' : 'end')
238 .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
239 .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + axisLabelDistance) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
240 .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding());
242 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
243 .data(scale.domain());
244 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
245 .style('opacity', 0);
246 axisMaxMin.exit().remove();
248 .attr('transform', function(d,i) {
249 return 'translate(0,' + scale0(d) + ')'
254 .attr('x', -axis.tickPadding())
255 .attr('text-anchor', 'end')
256 .text(function(d,i) {
262 //fmt = d3.format(',.2f');
266 return ('' + v).match('NaN') ? '' : v;
268 axisMaxMin.transition()
269 .attr('transform', function(d,i) {
270 return 'translate(0,' + scale.range()[i] + ')'
273 .style('opacity', 1);
278 .text(function(d) { return d });
281 if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
282 //check if max and min overlap other values, if so, hide the values that overlap
283 g.selectAll('g') // the g's wrapping each tick
284 .each(function(d,i) {
285 d3.select(this).select('text').attr('opacity', 1);
290 //fmt = d3.format(',.2f');
296 //d3.select(this).select('text').text(fmt(Math.pow(10,d)));
297 d3.select(this).select('text').text(v);
298 if (scale(d) < scale.range()[1] + 10 || scale(d) > scale.range()[0] - 10) { // 10 is assuming text height is 16... if d is 0, leave it!
299 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
300 d3.select(this).attr('opacity', 0);
302 d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
306 //if Max and Min = 0 only show min, Issue #281
307 if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0)
308 wrap.selectAll('g.nv-axisMaxMin')
309 .style('opacity', function(d,i) { return !i ? 1 : 0 });
313 if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
314 var maxMinRange = [];
315 wrap.selectAll('g.nv-axisMaxMin')
316 .each(function(d,i) {
318 if (i) // i== 1, max position
319 maxMinRange.push(scale(d) - this.getBBox().width - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
320 else // i==0, min position
321 maxMinRange.push(scale(d) + this.getBBox().width + 4)
323 if (i) // i== 1, max position
324 maxMinRange.push(scale(d) - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
325 else // i==0, min position
326 maxMinRange.push(scale(d) + 4)
329 g.selectAll('g') // the g's wrapping each tick
330 .each(function(d,i) {
335 //fmt = d3.format(',.2f');
342 //d3.select(this).select('text').text(fmt(Math.pow(10,d)));
343 d3.select(this).select('text').text(v);
345 if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
346 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
347 d3.select(this).remove();
349 d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
355 //highlight zero line ... Maybe should not be an option and should just be in CSS?
358 .filter(function(d) { return !parseFloat(Math.round(d.__data__*100000)/1000000) && (d.__data__ !== undefined) }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique
359 .classed('zero', true);
361 //store old scales for use in transitions on update
362 scale0 = scale.copy();
370 //============================================================
371 // Expose Public Variables
372 //------------------------------------------------------------
374 // expose chart's sub-components
377 d3.rebind(chart, axis, 'orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat');
378 d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); //these are also accessible by chart.scale(), but added common ones directly for ease of use
380 chart.options = nv.utils.optionsFunc.bind(chart);
382 chart.margin = function(_) {
383 if(!arguments.length) return margin;
384 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
385 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
386 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
387 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
391 chart.width = function(_) {
392 if (!arguments.length) return width;
397 chart.ticks = function(_) {
398 if (!arguments.length) return ticks;
403 chart.height = function(_) {
404 if (!arguments.length) return height;
409 chart.axisLabel = function(_) {
410 if (!arguments.length) return axisLabelText;
415 chart.showMaxMin = function(_) {
416 if (!arguments.length) return showMaxMin;
421 chart.logScale = function(_) {
422 if (!arguments.length) return logScale;
427 chart.highlightZero = function(_) {
428 if (!arguments.length) return highlightZero;
433 chart.scale = function(_) {
434 if (!arguments.length) return scale;
437 isOrdinal = typeof scale.rangeBands === 'function';
438 d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands');
442 chart.rotateYLabel = function(_) {
443 if(!arguments.length) return rotateYLabel;
448 chart.rotateLabels = function(_) {
449 if(!arguments.length) return rotateLabels;
454 chart.staggerLabels = function(_) {
455 if (!arguments.length) return staggerLabels;
460 chart.axisLabelDistance = function(_) {
461 if (!arguments.length) return axisLabelDistance;
462 axisLabelDistance = _;
466 //============================================================