| nv.models.axis = function() { |
| "use strict"; |
| //============================================================ |
| // Public Variables with Default Settings |
| //------------------------------------------------------------ |
| |
| var axis = d3.svg.axis() |
| ; |
| |
| var margin = {top: 0, right: 0, bottom: 0, left: 0} |
| , width = 75 //only used for tickLabel currently |
| , height = 60 //only used for tickLabel currently |
| , scale = d3.scale.linear() |
| , axisLabelText = null |
| , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes |
| , highlightZero = true |
| , rotateLabels = 0 |
| , rotateYLabel = true |
| , staggerLabels = false |
| , isOrdinal = false |
| , ticks = null |
| , axisLabelDistance = 12 //The larger this number is, the closer the axis label is to the axis. |
| ; |
| |
| axis |
| .scale(scale) |
| .orient('bottom') |
| .tickFormat(function(d) { return d }) |
| ; |
| |
| //============================================================ |
| |
| |
| //============================================================ |
| // Private Variables |
| //------------------------------------------------------------ |
| |
| var scale0; |
| |
| //============================================================ |
| |
| |
| function chart(selection) { |
| selection.each(function(data) { |
| var container = d3.select(this); |
| |
| |
| //------------------------------------------------------------ |
| // Setup containers and skeleton of chart |
| |
| var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]); |
| var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis'); |
| var gEnter = wrapEnter.append('g'); |
| var g = wrap.select('g') |
| |
| //------------------------------------------------------------ |
| |
| |
| if (ticks !== null) |
| axis.ticks(ticks); |
| else if (axis.orient() == 'top' || axis.orient() == 'bottom') |
| axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100); |
| |
| |
| //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component |
| |
| |
| g.transition().call(axis); |
| |
| scale0 = scale0 || axis.scale(); |
| |
| var fmt = axis.tickFormat(); |
| if (fmt == null) { |
| fmt = scale0.tickFormat(); |
| } |
| |
| var axisLabel = g.selectAll('text.nv-axislabel') |
| .data([axisLabelText || null]); |
| axisLabel.exit().remove(); |
| switch (axis.orient()) { |
| case 'top': |
| axisLabel.enter().append('text').attr('class', 'nv-axislabel'); |
| var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0])); |
| axisLabel |
| .attr('text-anchor', 'middle') |
| .attr('y', 0) |
| .attr('x', w/2); |
| if (showMaxMin) { |
| var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') |
| .data(scale.domain()); |
| axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text'); |
| axisMaxMin.exit().remove(); |
| axisMaxMin |
| .attr('transform', function(d,i) { |
| return 'translate(' + scale(d) + ',0)' |
| }) |
| .select('text') |
| .attr('dy', '-0.5em') |
| .attr('y', -axis.tickPadding()) |
| .attr('text-anchor', 'middle') |
| .text(function(d,i) { |
| var v = fmt(d); |
| return ('' + v).match('NaN') ? '' : v; |
| }); |
| axisMaxMin.transition() |
| .attr('transform', function(d,i) { |
| return 'translate(' + scale.range()[i] + ',0)' |
| }); |
| } |
| break; |
| case 'bottom': |
| var xLabelMargin = 36; |
| var maxTextWidth = 30; |
| var xTicks = g.selectAll('g').select("text"); |
| if (rotateLabels%360) { |
| //Calculate the longest xTick width |
| xTicks.each(function(d,i){ |
| var width = this.getBBox().width; |
| if(width > maxTextWidth) maxTextWidth = width; |
| }); |
| //Convert to radians before calculating sin. Add 30 to margin for healthy padding. |
| var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180)); |
| var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30; |
| //Rotate all xTicks |
| xTicks |
| .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' }) |
| .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end'); |
| } |
| axisLabel.enter().append('text').attr('class', 'nv-axislabel'); |
| var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0])); |
| axisLabel |
| .attr('text-anchor', 'middle') |
| .attr('y', xLabelMargin) |
| .attr('x', w/2); |
| if (showMaxMin) { |
| //if (showMaxMin && !isOrdinal) { |
| var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') |
| //.data(scale.domain()) |
| .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]); |
| axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text'); |
| axisMaxMin.exit().remove(); |
| axisMaxMin |
| .attr('transform', function(d,i) { |
| return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)' |
| }) |
| .select('text') |
| .attr('dy', '.71em') |
| .attr('y', axis.tickPadding()) |
| .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' }) |
| .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle') |
| .text(function(d,i) { |
| var v = fmt(d); |
| return ('' + v).match('NaN') ? '' : v; |
| }); |
| axisMaxMin.transition() |
| .attr('transform', function(d,i) { |
| //return 'translate(' + scale.range()[i] + ',0)' |
| //return 'translate(' + scale(d) + ',0)' |
| return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)' |
| }); |
| } |
| if (staggerLabels) |
| xTicks |
| .attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' }); |
| |
| break; |
| case 'right': |
| axisLabel.enter().append('text').attr('class', 'nv-axislabel'); |
| axisLabel |
| .style('text-anchor', rotateYLabel ? 'middle' : 'begin') |
| .attr('transform', rotateYLabel ? 'rotate(90)' : '') |
| .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 |
| .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding()); |
| if (showMaxMin) { |
| var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') |
| .data(scale.domain()); |
| axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text') |
| .style('opacity', 0); |
| axisMaxMin.exit().remove(); |
| axisMaxMin |
| .attr('transform', function(d,i) { |
| return 'translate(0,' + scale(d) + ')' |
| }) |
| .select('text') |
| .attr('dy', '.32em') |
| .attr('y', 0) |
| .attr('x', axis.tickPadding()) |
| .style('text-anchor', 'start') |
| .text(function(d,i) { |
| var v = fmt(d); |
| return ('' + v).match('NaN') ? '' : v; |
| }); |
| axisMaxMin.transition() |
| .attr('transform', function(d,i) { |
| return 'translate(0,' + scale.range()[i] + ')' |
| }) |
| .select('text') |
| .style('opacity', 1); |
| } |
| break; |
| case 'left': |
| /* |
| //For dynamically placing the label. Can be used with dynamically-sized chart axis margins |
| var yTicks = g.selectAll('g').select("text"); |
| yTicks.each(function(d,i){ |
| var labelPadding = this.getBBox().width + axis.tickPadding() + 16; |
| if(labelPadding > width) width = labelPadding; |
| }); |
| */ |
| axisLabel.enter().append('text').attr('class', 'nv-axislabel'); |
| axisLabel |
| .style('text-anchor', rotateYLabel ? 'middle' : 'end') |
| .attr('transform', rotateYLabel ? 'rotate(-90)' : '') |
| .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 |
| .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding()); |
| if (showMaxMin) { |
| var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') |
| .data(scale.domain()); |
| axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text') |
| .style('opacity', 0); |
| axisMaxMin.exit().remove(); |
| axisMaxMin |
| .attr('transform', function(d,i) { |
| return 'translate(0,' + scale0(d) + ')' |
| }) |
| .select('text') |
| .attr('dy', '.32em') |
| .attr('y', 0) |
| .attr('x', -axis.tickPadding()) |
| .attr('text-anchor', 'end') |
| .text(function(d,i) { |
| var v = fmt(d); |
| return ('' + v).match('NaN') ? '' : v; |
| }); |
| axisMaxMin.transition() |
| .attr('transform', function(d,i) { |
| return 'translate(0,' + scale.range()[i] + ')' |
| }) |
| .select('text') |
| .style('opacity', 1); |
| } |
| break; |
| } |
| axisLabel |
| .text(function(d) { return d }); |
| |
| |
| if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) { |
| //check if max and min overlap other values, if so, hide the values that overlap |
| g.selectAll('g') // the g's wrapping each tick |
| .each(function(d,i) { |
| d3.select(this).select('text').attr('opacity', 1); |
| 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! |
| if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL |
| d3.select(this).attr('opacity', 0); |
| |
| d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!! |
| } |
| }); |
| |
| //if Max and Min = 0 only show min, Issue #281 |
| if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) |
| wrap.selectAll('g.nv-axisMaxMin') |
| .style('opacity', function(d,i) { return !i ? 1 : 0 }); |
| |
| } |
| |
| if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) { |
| var maxMinRange = []; |
| wrap.selectAll('g.nv-axisMaxMin') |
| .each(function(d,i) { |
| try { |
| if (i) // i== 1, max position |
| 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) |
| else // i==0, min position |
| maxMinRange.push(scale(d) + this.getBBox().width + 4) |
| }catch (err) { |
| if (i) // i== 1, max position |
| 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) |
| else // i==0, min position |
| maxMinRange.push(scale(d) + 4) |
| } |
| }); |
| g.selectAll('g') // the g's wrapping each tick |
| .each(function(d,i) { |
| if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) { |
| if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL |
| d3.select(this).remove(); |
| else |
| d3.select(this).select('text').remove(); // Don't remove the ZERO line!! |
| } |
| }); |
| } |
| |
| |
| //highlight zero line ... Maybe should not be an option and should just be in CSS? |
| if (highlightZero) |
| g.selectAll('.tick') |
| .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 |
| .classed('zero', true); |
| |
| //store old scales for use in transitions on update |
| scale0 = scale.copy(); |
| |
| }); |
| |
| return chart; |
| } |
| |
| |
| //============================================================ |
| // Expose Public Variables |
| //------------------------------------------------------------ |
| |
| // expose chart's sub-components |
| chart.axis = axis; |
| |
| d3.rebind(chart, axis, 'orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat'); |
| d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); //these are also accessible by chart.scale(), but added common ones directly for ease of use |
| |
| chart.options = nv.utils.optionsFunc.bind(chart); |
| |
| chart.margin = function(_) { |
| if(!arguments.length) return margin; |
| margin.top = typeof _.top != 'undefined' ? _.top : margin.top; |
| margin.right = typeof _.right != 'undefined' ? _.right : margin.right; |
| margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; |
| margin.left = typeof _.left != 'undefined' ? _.left : margin.left; |
| return chart; |
| } |
| |
| chart.width = function(_) { |
| if (!arguments.length) return width; |
| width = _; |
| return chart; |
| }; |
| |
| chart.ticks = function(_) { |
| if (!arguments.length) return ticks; |
| ticks = _; |
| return chart; |
| }; |
| |
| chart.height = function(_) { |
| if (!arguments.length) return height; |
| height = _; |
| return chart; |
| }; |
| |
| chart.axisLabel = function(_) { |
| if (!arguments.length) return axisLabelText; |
| axisLabelText = _; |
| return chart; |
| } |
| |
| chart.showMaxMin = function(_) { |
| if (!arguments.length) return showMaxMin; |
| showMaxMin = _; |
| return chart; |
| } |
| |
| chart.highlightZero = function(_) { |
| if (!arguments.length) return highlightZero; |
| highlightZero = _; |
| return chart; |
| } |
| |
| chart.scale = function(_) { |
| if (!arguments.length) return scale; |
| scale = _; |
| axis.scale(scale); |
| isOrdinal = typeof scale.rangeBands === 'function'; |
| d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); |
| return chart; |
| } |
| |
| chart.rotateYLabel = function(_) { |
| if(!arguments.length) return rotateYLabel; |
| rotateYLabel = _; |
| return chart; |
| } |
| |
| chart.rotateLabels = function(_) { |
| if(!arguments.length) return rotateLabels; |
| rotateLabels = _; |
| return chart; |
| } |
| |
| chart.staggerLabels = function(_) { |
| if (!arguments.length) return staggerLabels; |
| staggerLabels = _; |
| return chart; |
| }; |
| |
| chart.axisLabelDistance = function(_) { |
| if (!arguments.length) return axisLabelDistance; |
| axisLabelDistance = _; |
| return chart; |
| }; |
| |
| //============================================================ |
| |
| |
| return chart; |
| } |