nvd3-fork
Version:
FORK! of NVD3, a reusable charting library written in d3.js
397 lines (339 loc) • 15.8 kB
JavaScript
nv.models.scatterChart = function() {
"use strict";
//============================================================
// Public Variables with Default Settings
//------------------------------------------------------------
var scatter = nv.models.scatter()
, xAxis = nv.models.axis()
, yAxis = nv.models.axis()
, legend = nv.models.legend()
, distX = nv.models.distribution()
, distY = nv.models.distribution()
, tooltip = nv.models.tooltip()
;
var margin = {top: 30, right: 20, bottom: 50, left: 75}
, marginTop = null
, width = null
, height = null
, container = null
, color = nv.utils.defaultColor()
, x = scatter.xScale()
, y = scatter.yScale()
, showDistX = false
, showDistY = false
, showLegend = true
, showXAxis = true
, showYAxis = true
, rightAlignYAxis = false
, state = nv.utils.state()
, defaultState = null
, dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
, noData = null
, duration = 250
, showLabels = false
;
scatter.xScale(x).yScale(y);
xAxis.orient('bottom').tickPadding(10);
yAxis
.orient((rightAlignYAxis) ? 'right' : 'left')
.tickPadding(10)
;
distX.axis('x');
distY.axis('y');
tooltip
.headerFormatter(function(d, i) {
return xAxis.tickFormat()(d, i);
})
.valueFormatter(function(d, i) {
return yAxis.tickFormat()(d, i);
});
//============================================================
// Private Variables
//------------------------------------------------------------
var x0, y0
, renderWatch = nv.utils.renderWatch(dispatch, duration);
var stateGetter = function(data) {
return function(){
return {
active: data.map(function(d) { return !d.disabled })
};
}
};
var stateSetter = function(data) {
return function(state) {
if (state.active !== undefined)
data.forEach(function(series,i) {
series.disabled = !state.active[i];
});
}
};
function chart(selection) {
renderWatch.reset();
renderWatch.models(scatter);
if (showXAxis) renderWatch.models(xAxis);
if (showYAxis) renderWatch.models(yAxis);
if (showDistX) renderWatch.models(distX);
if (showDistY) renderWatch.models(distY);
selection.each(function(data) {
var that = this;
container = d3.select(this);
nv.utils.initSVG(container);
var availableWidth = nv.utils.availableWidth(width, container, margin),
availableHeight = nv.utils.availableHeight(height, container, margin);
chart.update = function() {
if (duration === 0)
container.call(chart);
else
container.transition().duration(duration).call(chart);
};
chart.container = this;
state
.setter(stateSetter(data), chart.update)
.getter(stateGetter(data))
.update();
// DEPRECATED set state.disableddisabled
state.disabled = data.map(function(d) { return !!d.disabled });
if (!defaultState) {
var key;
defaultState = {};
for (key in state) {
if (state[key] instanceof Array)
defaultState[key] = state[key].slice(0);
else
defaultState[key] = state[key];
}
}
// Display noData message if there's nothing to show.
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
nv.utils.noData(chart, container);
renderWatch.renderEnd('scatter immediate');
return chart;
} else {
container.selectAll('.nv-noData').remove();
}
// Setup Scales
x = scatter.xScale();
y = scatter.yScale();
// Setup containers and skeleton of chart
var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
var gEnter = wrapEnter.append('g');
var g = wrap.select('g');
// background for pointer events
gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
gEnter.append('g').attr('class', 'nv-x nv-axis');
gEnter.append('g').attr('class', 'nv-y nv-axis');
gEnter.append('g').attr('class', 'nv-scatterWrap');
gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
gEnter.append('g').attr('class', 'nv-distWrap');
gEnter.append('g').attr('class', 'nv-legendWrap');
if (rightAlignYAxis) {
g.select(".nv-y.nv-axis")
.attr("transform", "translate(" + availableWidth + ",0)");
}
// Legend
if (!showLegend) {
g.select('.nv-legendWrap').selectAll('*').remove();
} else {
var legendWidth = availableWidth;
legend.width(legendWidth);
wrap.select('.nv-legendWrap')
.datum(data)
.call(legend);
if (!marginTop && legend.height() !== margin.top) {
margin.top = legend.height();
availableHeight = nv.utils.availableHeight(height, container, margin);
}
wrap.select('.nv-legendWrap')
.attr('transform', 'translate(0' + ',' + (-margin.top) +')');
}
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Main Chart Component(s)
scatter
.width(availableWidth)
.height(availableHeight)
.color(data.map(function(d,i) {
d.color = d.color || color(d, i);
return d.color;
}).filter(function(d,i) { return !data[i].disabled }))
.showLabels(showLabels);
wrap.select('.nv-scatterWrap')
.datum(data.filter(function(d) { return !d.disabled }))
.call(scatter);
wrap.select('.nv-regressionLinesWrap')
.attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
.data(function (d) {
return d;
});
regWrap.enter().append('g').attr('class', 'nv-regLines');
var regLine = regWrap.selectAll('.nv-regLine')
.data(function (d) {
return [d]
});
regLine.enter()
.append('line').attr('class', 'nv-regLine')
.style('stroke-opacity', 0);
// don't add lines unless we have slope and intercept to use
regLine.filter(function(d) {
return d.intercept && d.slope;
})
.watchTransition(renderWatch, 'scatterPlusLineChart: regline')
.attr('x1', x.range()[0])
.attr('x2', x.range()[1])
.attr('y1', function (d, i) {
return y(x.domain()[0] * d.slope + d.intercept)
})
.attr('y2', function (d, i) {
return y(x.domain()[1] * d.slope + d.intercept)
})
.style('stroke', function (d, i, j) {
return color(d, j)
})
.style('stroke-opacity', function (d, i) {
return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
});
// Setup Axes
if (showXAxis) {
xAxis
.scale(x)
._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
.tickSize( -availableHeight , 0);
g.select('.nv-x.nv-axis')
.attr('transform', 'translate(0,' + y.range()[0] + ')')
.call(xAxis);
}
if (showYAxis) {
yAxis
.scale(y)
._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
.tickSize( -availableWidth, 0);
g.select('.nv-y.nv-axis')
.call(yAxis);
}
// Setup Distribution
distX
.getData(scatter.x())
.scale(x)
.width(availableWidth)
.color(data.map(function(d,i) {
return d.color || color(d, i);
}).filter(function(d,i) { return !data[i].disabled }));
gEnter.select('.nv-distWrap').append('g')
.attr('class', 'nv-distributionX');
g.select('.nv-distributionX')
.attr('transform', 'translate(0,' + y.range()[0] + ')')
.datum(data.filter(function(d) { return !d.disabled }))
.call(distX)
.style('opacity', function() { return showDistX ? '1' : '1e-6'; })
.watchTransition(renderWatch, 'scatterPlusLineChart')
.style('opacity', function() { return showDistX ? '1' : '1e-6'; })
distY
.getData(scatter.y())
.scale(y)
.width(availableHeight)
.color(data.map(function(d,i) {
return d.color || color(d, i);
}).filter(function(d,i) { return !data[i].disabled }));
gEnter.select('.nv-distWrap').append('g')
.attr('class', 'nv-distributionY');
g.select('.nv-distributionY')
.attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
.datum(data.filter(function(d) { return !d.disabled }))
.call(distY)
.style('opacity', function() { return showDistY ? '1' : '1e-6'; })
.watchTransition(renderWatch, 'scatterPlusLineChart')
.style('opacity', function() { return showDistY ? '1' : '1e-6'; })
//============================================================
// Event Handling/Dispatching (in chart's scope)
//------------------------------------------------------------
legend.dispatch.on('stateChange', function(newState) {
for (var key in newState)
state[key] = newState[key];
dispatch.stateChange(state);
chart.update();
});
// Update chart from a state object passed to event handler
dispatch.on('changeState', function(e) {
if (typeof e.disabled !== 'undefined') {
data.forEach(function(series,i) {
series.disabled = e.disabled[i];
});
state.disabled = e.disabled;
}
chart.update();
});
// mouseover needs availableHeight so we just keep scatter mouse events inside the chart block
scatter.dispatch.on('elementMouseout.tooltip', function(evt) {
tooltip.hidden(true);
container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
.attr('y1', 0);
container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
.attr('x2', distY.size());
});
scatter.dispatch.on('elementMouseover.tooltip', function(evt) {
container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
.attr('y1', evt.relativePos[1] - availableHeight);
container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
.attr('x2', evt.relativePos[0] + distX.size());
tooltip.data(evt).hidden(false);
});
//store old scales for use in transitions on update
x0 = x.copy();
y0 = y.copy();
});
renderWatch.renderEnd('scatter with line immediate');
return chart;
}
//============================================================
// Expose Public Variables
//------------------------------------------------------------
// expose chart's sub-components
chart.dispatch = dispatch;
chart.scatter = scatter;
chart.legend = legend;
chart.xAxis = xAxis;
chart.yAxis = yAxis;
chart.distX = distX;
chart.distY = distY;
chart.tooltip = tooltip;
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=_;}},
container: {get: function(){return container;}, set: function(_){container=_;}},
showDistX: {get: function(){return showDistX;}, set: function(_){showDistX=_;}},
showDistY: {get: function(){return showDistY;}, set: function(_){showDistY=_;}},
showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
noData: {get: function(){return noData;}, set: function(_){noData=_;}},
duration: {get: function(){return duration;}, set: function(_){duration=_;}},
showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
// options that require extra logic in the setter
margin: {get: function(){return margin;}, set: function(_){
if (_.top !== undefined) {
margin.top = _.top;
marginTop = _.top;
}
margin.right = _.right !== undefined ? _.right : margin.right;
margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
margin.left = _.left !== undefined ? _.left : margin.left;
}},
rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
rightAlignYAxis = _;
yAxis.orient( (_) ? 'right' : 'left');
}},
color: {get: function(){return color;}, set: function(_){
color = nv.utils.getColor(_);
legend.color(color);
distX.color(color);
distY.color(color);
}}
});
nv.utils.inheritOptions(chart, scatter);
nv.utils.initOptions(chart);
return chart;
};