UNPKG

nvd3-fork

Version:

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

326 lines (296 loc) 15.8 kB
nv.models.boxPlot = function() { "use strict"; //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ var margin = {top: 0, right: 0, bottom: 0, left: 0}, width = 960, height = 500, id = Math.floor(Math.random() * 10000), // Create semi-unique ID in case user doesn't select one xScale = d3.scale.ordinal(), yScale = d3.scale.linear(), getX = function(d) { return d.label }, // Default data model selectors. getQ1 = function(d) { return d.values.Q1 }, getQ2 = function(d) { return d.values.Q2 }, getQ3 = function(d) { return d.values.Q3 }, getWl = function(d) { return d.values.whisker_low }, getWh = function(d) { return d.values.whisker_high }, getColor = function(d) { return d.color }, getOlItems = function(d) { return d.values.outliers }, getOlValue = function(d, i, j) { return d }, getOlLabel = function(d, i, j) { return d }, getOlColor = function(d, i, j) { return undefined }, color = nv.utils.defaultColor(), container = null, xDomain, xRange, yDomain, yRange, dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd'), duration = 250, maxBoxWidth = null; //============================================================ // Private Variables //------------------------------------------------------------ var xScale0, yScale0; var renderWatch = nv.utils.renderWatch(dispatch, duration); function chart(selection) { renderWatch.reset(); selection.each(function(data) { var availableWidth = width - margin.left - margin.right, availableHeight = height - margin.top - margin.bottom; container = d3.select(this); nv.utils.initSVG(container); // Setup Scales xScale.domain(xDomain || data.map(function(d,i) { return getX(d,i); })) .rangeBands(xRange || [0, availableWidth], 0.1); // if we know yDomain, no need to calculate var yData = [] if (!yDomain) { // (y-range is based on quartiles, whiskers and outliers) var values = [], yMin, yMax; data.forEach(function (d, i) { var q1 = getQ1(d), q3 = getQ3(d), wl = getWl(d), wh = getWh(d); var olItems = getOlItems(d); if (olItems) { olItems.forEach(function (e, i) { values.push(getOlValue(e, i, undefined)); }); } if (wl) { values.push(wl) } if (q1) { values.push(q1) } if (q3) { values.push(q3) } if (wh) { values.push(wh) } }); yMin = d3.min(values); yMax = d3.max(values); yData = [ yMin, yMax ] ; } yScale.domain(yDomain || yData); yScale.range(yRange || [availableHeight, 0]); //store old scales if they exist xScale0 = xScale0 || xScale; yScale0 = yScale0 || yScale.copy().range([yScale(0),yScale(0)]); // Setup containers and skeleton of chart var wrap = container.selectAll('g.nv-wrap').data([data]); var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap'); wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); var boxplots = wrap.selectAll('.nv-boxplot').data(function(d) { return d }); var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6); boxplots .attr('class', 'nv-boxplot') .attr('transform', function(d,i,j) { return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)'; }) .classed('hover', function(d) { return d.hover }); boxplots .watchTransition(renderWatch, 'nv-boxplot: boxplots') .style('stroke-opacity', 1) .style('fill-opacity', 0.75) .delay(function(d,i) { return i * duration / data.length }) .attr('transform', function(d,i) { return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)'; }); boxplots.exit().remove(); // ----- add the SVG elements for each boxPlot ----- // conditionally append whisker lines boxEnter.each(function(d,i) { var box = d3.select(this); [getWl, getWh].forEach(function (f) { if (f(d) !== undefined && f(d) !== null) { var key = (f === getWl) ? 'low' : 'high'; box.append('line') .style('stroke', getColor(d) || color(d,i)) .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key); box.append('line') .style('stroke', getColor(d) || color(d,i)) .attr('class', 'nv-boxplot-tick nv-boxplot-' + key); } }); }); var box_width = function() { return (maxBoxWidth === null ? xScale.rangeBand() * 0.9 : Math.min(75, xScale.rangeBand() * 0.9)); }; var box_left = function() { return xScale.rangeBand() * 0.45 - box_width()/2; }; var box_right = function() { return xScale.rangeBand() * 0.45 + box_width()/2; }; // update whisker lines and ticks [getWl, getWh].forEach(function (f) { var key = (f === getWl) ? 'low' : 'high'; var endpoint = (f === getWl) ? getQ1 : getQ3; boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key) .watchTransition(renderWatch, 'nv-boxplot: boxplots') .attr('x1', xScale.rangeBand() * 0.45 ) .attr('y1', function(d,i) { return yScale(f(d)); }) .attr('x2', xScale.rangeBand() * 0.45 ) .attr('y2', function(d,i) { return yScale(endpoint(d)); }); boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key) .watchTransition(renderWatch, 'nv-boxplot: boxplots') .attr('x1', box_left ) .attr('y1', function(d,i) { return yScale(f(d)); }) .attr('x2', box_right ) .attr('y2', function(d,i) { return yScale(f(d)); }); }); [getWl, getWh].forEach(function (f) { var key = (f === getWl) ? 'low' : 'high'; boxEnter.selectAll('.nv-boxplot-' + key) .on('mouseover', function(d,i,j) { d3.select(this).classed('hover', true); dispatch.elementMouseover({ series: { key: f(d), color: getColor(d) || color(d,j) }, e: d3.event }); }) .on('mouseout', function(d,i,j) { d3.select(this).classed('hover', false); dispatch.elementMouseout({ series: { key: f(d), color: getColor(d) || color(d,j) }, e: d3.event }); }) .on('mousemove', function(d,i) { dispatch.elementMousemove({e: d3.event}); }); }); // boxes boxEnter.append('rect') .attr('class', 'nv-boxplot-box') // tooltip events .on('mouseover', function(d,i) { d3.select(this).classed('hover', true); dispatch.elementMouseover({ key: getX(d), value: getX(d), series: [ { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) }, { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) }, { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) } ], data: d, index: i, e: d3.event }); }) .on('mouseout', function(d,i) { d3.select(this).classed('hover', false); dispatch.elementMouseout({ key: getX(d), value: getX(d), series: [ { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) }, { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) }, { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) } ], data: d, index: i, e: d3.event }); }) .on('mousemove', function(d,i) { dispatch.elementMousemove({e: d3.event}); }); // box transitions boxplots.select('rect.nv-boxplot-box') .watchTransition(renderWatch, 'nv-boxplot: boxes') .attr('y', function(d,i) { return yScale(getQ3(d)); }) .attr('width', box_width) .attr('x', box_left ) .attr('height', function(d,i) { return Math.abs(yScale(getQ3(d)) - yScale(getQ1(d))) || 1 }) .style('fill', function(d,i) { return getColor(d) || color(d,i) }) .style('stroke', function(d,i) { return getColor(d) || color(d,i) }); // median line boxEnter.append('line').attr('class', 'nv-boxplot-median'); boxplots.select('line.nv-boxplot-median') .watchTransition(renderWatch, 'nv-boxplot: boxplots line') .attr('x1', box_left) .attr('y1', function(d,i) { return yScale(getQ2(d)); }) .attr('x2', box_right) .attr('y2', function(d,i) { return yScale(getQ2(d)); }); // outliers var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) { return getOlItems(d) || []; }); outliers.enter().append('circle') .style('fill', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) }) .style('stroke', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) }) .style('z-index', 9000) .on('mouseover', function(d,i,j) { d3.select(this).classed('hover', true); dispatch.elementMouseover({ series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) }, e: d3.event }); }) .on('mouseout', function(d,i,j) { d3.select(this).classed('hover', false); dispatch.elementMouseout({ series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) }, e: d3.event }); }) .on('mousemove', function(d,i) { dispatch.elementMousemove({e: d3.event}); }); outliers.attr('class', 'nv-boxplot-outlier'); outliers .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier') .attr('cx', xScale.rangeBand() * 0.45) .attr('cy', function(d,i,j) { return yScale(getOlValue(d,i,j)); }) .attr('r', '3'); outliers.exit().remove(); //store old scales for use in transitions on update xScale0 = xScale.copy(); yScale0 = yScale.copy(); }); renderWatch.renderEnd('nv-boxplot immediate'); return chart; } //============================================================ // Expose Public Variables //------------------------------------------------------------ chart.dispatch = dispatch; chart.options = nv.utils.optionsFunc.bind(chart); chart._options = Object.create({}, { // simple options, just get/set the necessary values width: {get: function(){return width;}, set: function(_){width=_;}}, height: {get: function(){return height;}, set: function(_){height=_;}}, maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}}, x: {get: function(){return getX;}, set: function(_){getX=_;}}, q1: {get: function(){return getQ1;}, set: function(_){getQ1=_;}}, q2: {get: function(){return getQ2;}, set: function(_){getQ2=_;}}, q3: {get: function(){return getQ3;}, set: function(_){getQ3=_;}}, wl: {get: function(){return getWl;}, set: function(_){getWl=_;}}, wh: {get: function(){return getWh;}, set: function(_){getWh=_;}}, itemColor: {get: function(){return getColor;}, set: function(_){getColor=_;}}, outliers: {get: function(){return getOlItems;}, set: function(_){getOlItems=_;}}, outlierValue: {get: function(){return getOlValue;}, set: function(_){getOlValue=_;}}, outlierLabel: {get: function(){return getOlLabel;}, set: function(_){getOlLabel=_;}}, outlierColor: {get: function(){return getOlColor;}, set: function(_){getOlColor=_;}}, xScale: {get: function(){return xScale;}, set: function(_){xScale=_;}}, yScale: {get: function(){return yScale;}, set: function(_){yScale=_;}}, xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, id: {get: function(){return id;}, set: function(_){id=_;}}, // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}}, y: { get: function() { console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.'); return {}; }, set: function(_) { console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.'); } }, // 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; }}, color: {get: function(){return color;}, set: function(_){ color = nv.utils.getColor(_); }}, duration: {get: function(){return duration;}, set: function(_){ duration = _; renderWatch.reset(duration); }} }); nv.utils.initOptions(chart); return chart; };