added multiBarWithLegend to makefile, and tweaked the animation a little on the multibar

master-patched
Bob Monteverde 12 years ago
parent af750e7be3
commit 7857c4ab6b

@ -14,6 +14,7 @@ JS_FILES = \
src/models/lineWithFocus.js \
src/models/lineWithLegend.js \
src/models/multiBar.js \
src/models/multiBarWithLegend.js \
src/models/pie.js \
src/models/scatter.js \
src/models/scatterWithLegend.js \

@ -41,7 +41,7 @@ text {
<script src="stream_layers.js"></script>
<script>
var test_data = stream_layers(3,12,.1).map(function(data, i) {
var test_data = stream_layers(3,128,.1).map(function(data, i) {
return {
key: 'Stream' + i,
values: data

@ -262,10 +262,12 @@ nv.models.axis = function() {
function chart(selection) {
selection.each(function(data) {
scale.domain(domain)
.range(range);
//scale.domain(domain)
//.range(range);
axis.orient(orient);
//axis.orient(orient);
if (orient == 'top' || orient == 'bottom')
axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
//TODO: consider calculating height based on whether or not label is added, for reference in charts using this component
@ -277,28 +279,28 @@ nv.models.axis = function() {
.attr('text-anchor', 'middle')
.attr('y', 0);
axisLabel
.attr('x', range[1] / 2);
.attr('x', scale.range()[1] / 2);
break;
case 'right':
axisLabel.enter().append('text').attr('class', 'axislabel')
.attr('transform', 'rotate(90)')
.attr('y', -40); //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
axisLabel
.attr('x', -range[0] / 2);
.attr('x', -scale.range()[0] / 2);
break;
case 'bottom':
axisLabel.enter().append('text').attr('class', 'axislabel')
.attr('text-anchor', 'middle')
.attr('y', 25);
axisLabel
.attr('x', range[1] / 2);
.attr('x', scale.range()[1] / 2);
break;
case 'left':
axisLabel.enter().append('text').attr('class', 'axislabel')
.attr('transform', 'rotate(-90)')
.attr('y', -40); //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
axisLabel
.attr('x', -range[0] / 2);
.attr('x', -scale.range()[0] / 2);
break;
}
axisLabel.exit().remove();
@ -310,11 +312,12 @@ nv.models.axis = function() {
d3.transition(d3.select(this))
.call(axis);
d3.select(this)
.selectAll('line.tick')
//.filter(function(d) { return !parseFloat(d) })
.filter(function(d) { return !parseFloat(Math.round(d*100000)/1000000) }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique
.classed('zero', true);
if (orient == 'left' || orient == 'right')
d3.select(this)
.selectAll('line.tick')
//.filter(function(d) { return !parseFloat(d) })
.filter(function(d) { return !parseFloat(Math.round(d*100000)/1000000) }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique
.classed('zero', true);
});
@ -322,21 +325,25 @@ nv.models.axis = function() {
}
//TODO: orient, domain, and range could be rebind's... but then they won't return the chart component
chart.orient = function(_) {
if (!arguments.length) return orient;
orient = _;
axis.orient(_);
return chart;
};
chart.domain = function(_) {
if (!arguments.length) return domain;
domain = _;
//domain = _;
scale.domain(_);
return chart;
};
chart.range = function(_) {
if (!arguments.length) return range;
range = _;
//range = _;
scale.range(_);
return chart;
};
@ -353,7 +360,7 @@ nv.models.axis = function() {
return chart;
}
//d3.rebind(chart, scale, 'domain', 'range', 'rangRoundBands', 'rangeBands'); //would implement, but will break because domain and range won't return chart... will likelu implement later
d3.rebind(chart, axis, 'ticks', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat');
return chart;
@ -1895,13 +1902,8 @@ nv.models.linePlusBar = function() {
bars.dispatch.on('elementMouseover.tooltip', function(e) {
dispatch.tooltipShow({
point: e.point,
series: e.series,
pos: [e.pos[0] + margin.left, e.pos[1] + margin.top],
seriesIndex: e.seriesIndex,
pointIndex: e.pointIndex
});
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
dispatch.tooltipShow(e);
});
bars.dispatch.on('elementMouseout.tooltip', function(e) {
@ -2579,7 +2581,8 @@ nv.models.multiBar = function() {
clipEdge = true,
stacked = false,
color = d3.scale.category20().range(),
xDomain, yDomain;
xDomain, yDomain,
x0, y0;
//var x = d3.scale.linear(),
var x = d3.scale.ordinal(), //TODO: Need to figure out how to use axis model with ordinal scale
@ -2610,17 +2613,29 @@ nv.models.multiBar = function() {
availableWidth = width - margin.left - margin.right,
availableHeight = height - margin.top - margin.bottom;
//store old scales if they exist
x0 = x0 || x;
y0 = y0 || y;
//add series index to each data point for reference
data = data.map(function(series, i) {
series.values = series.values.map(function(point) {
point.series = i;
return point;
});
return series;
});
//x .domain(xDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.x }).concat(forceX)))
x .domain(d3.merge(seriesData).map(function(d) { return d.x }))
//x .domain(seriesData[0].map(function(d) { return d.x }))
.rangeRoundBands([0, availableWidth], .1);
.rangeBands([0, availableWidth], .1);
//.rangeRoundBands([0, availableWidth], .1);
//.range([0, availableWidth]);
//y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (d.y0 || 0) }).concat(forceY)))
y .domain(yDomain || [0,d3.max(d3.merge(seriesData).map(function(d) { return d.y + (d.y0 || 0) }).concat(forceY))])
y .domain(yDomain || [0,d3.max(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY))])
.range([availableHeight, 0]);
@ -2661,7 +2676,7 @@ nv.models.multiBar = function() {
.attr('transform', function(d,i) {
return stacked ?
'translate(0,0)'
: 'translate(' + (i * x.rangeBand(.9) / data.length ) + ',0)'
: 'translate(' + (i * x.rangeBand() / data.length ) + ',0)'
});
d3.transition(groups.exit())
.style('stroke-opacity', 1e-6)
@ -2676,10 +2691,10 @@ nv.models.multiBar = function() {
.attr('transform', function(d,i) {
return stacked ?
'translate(0,0)'
: 'translate(' + (i * x.rangeBand(.9) / data.length ) + ',0)'
: 'translate(' + (i * x.rangeBand() / data.length ) + ',0)'
})
.style('stroke-opacity', 1)
.style('fill-opacity', .5);
.style('fill-opacity', .75);
var bars = groups.selectAll('rect.bar')
@ -2692,18 +2707,19 @@ nv.models.multiBar = function() {
.attr('class', function(d,i) { return getY(d,i) < 0 ? 'bar negative' : 'bar positive'})
//.attr('fill', function(d,i) { return color[0]; })
.attr('x', 0 )
.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
//.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
//.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) })
.attr('y', y(0))
.attr('y', function(d) { return y0(stacked ? d.y0 : 0) })
.attr('height', 0)
.attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
.on('mouseover', function(d,i) {
d3.select(this).classed('hover', true);
dispatch.elementMouseover({
point: d,
series: data[0],
pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
series: data[d.series],
pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
pointIndex: i,
seriesIndex: 0,
seriesIndex: d.series,
e: d3.event
});
@ -2712,9 +2728,9 @@ nv.models.multiBar = function() {
d3.select(this).classed('hover', false);
dispatch.elementMouseout({
point: d,
series: data[0],
series: data[d.series],
pointIndex: i,
seriesIndex: 0,
seriesIndex: d.series,
e: d3.event
});
})
@ -2747,15 +2763,24 @@ nv.models.multiBar = function() {
//.attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - x(.5)) + ',0)'; })
.attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
//.attr('width', x(.9) / data.length ) //TODO: this should not assume that each consecutive bar x = x + 1
.attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
d3.transition(bars)
.attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
.attr('y', function(d,i) {
return y(getY(d,i) + (d.y0 || 0));
return y(getY(d,i) + (stacked ? d.y0 : 0));
})
.attr('height', function(d,i) {
return Math.abs(y(d.y + (d.y0 || 0)) - y((d.y0 || 0)))
return Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0)))
});
chart.update = function() {
selection.transition().call(chart);
}
//store old scales for use in transitions on update, to animate from old to new positions, and sizes
x0 = x.copy();
y0 = y.copy();
});
return chart;
@ -2844,6 +2869,229 @@ nv.models.multiBar = function() {
return chart;
}
nv.models.multiBarWithLegend = function() {
var margin = {top: 30, right: 20, bottom: 50, left: 60},
width = function() { return 960 },
height = function() { return 500 },
color = d3.scale.category20().range();
//var x = d3.scale.linear(),
var x = d3.scale.ordinal(),
y = d3.scale.linear(),
xAxis = nv.models.axis().scale(x).orient('bottom'),
yAxis = nv.models.axis().scale(y).orient('left'),
legend = nv.models.legend().height(30),
controls = nv.models.legend().height(30),
multibar = nv.models.multiBar().stacked(false),
dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
//TODO: let user select default
var controlsData = [
{ key: 'Grouped' },
{ key: 'Stacked', disabled: true },
];
function chart(selection) {
selection.each(function(data) {
var availableWidth = width() - margin.left - margin.right,
availableHeight = height() - margin.top - margin.bottom,
seriesData;
if (multibar.stacked()) {
seriesData = data.filter(function(d) { return !d.disabled })
.reduce(function(prev, curr, index) { //sum up all the y's
curr.values.forEach(function(d,i) {
if (!index) prev[i] = {x: multibar.x()(d,i), y:0};
prev[i].y += multibar.y()(d,i);
});
return prev;
}, []);
} else {
seriesData = data.filter(function(d) { return !d.disabled })
.map(function(d) {
return d.values.map(function(d,i) {
return { x: multibar.x()(d,i), y: multibar.y()(d,i) }
})
});
}
//x .domain(d3.extent(d3.merge(seriesData).map(function(d) { return d.x }).concat(multibar.forceX) ))
//.range([0, availableWidth]);
x .domain(d3.merge(seriesData).map(function(d) { return d.x }))
.rangeBands([0, availableWidth], .1);
//.rangeRoundBands([0, availableWidth], .1);
y .domain(d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(multibar.forceY) ))
.range([availableHeight, 0]);
multibar
.width(availableWidth)
.height(availableHeight)
//.xDomain(x.domain())
//.yDomain(y.domain())
.color(data.map(function(d,i) {
return d.color || color[i % 10];
}).filter(function(d,i) { return !data[i].disabled }))
var wrap = d3.select(this).selectAll('g.wrap').data([data]);
var gEnter = wrap.enter().append('g').attr('class', 'wrap d3lineWithLegend').append('g');
gEnter.append('g').attr('class', 'x axis');
gEnter.append('g').attr('class', 'y axis');
gEnter.append('g').attr('class', 'linesWrap');
gEnter.append('g').attr('class', 'legendWrap');
gEnter.append('g').attr('class', 'controlsWrap');
//TODO: margins should be adjusted based on what components are used: axes, axis labels, legend
margin.top = legend.height();
var g = wrap.select('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
legend.width(availableWidth / 2);
g.select('.legendWrap')
.datum(data)
.attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')')
.call(legend);
controls.width(180).color(['#444', '#444', '#444']);
g.select('.controlsWrap')
.datum(controlsData)
.attr('transform', 'translate(0,' + (-margin.top) +')')
.call(controls);
var linesWrap = g.select('.linesWrap')
.datum(data.filter(function(d) { return !d.disabled }))
d3.transition(linesWrap).call(multibar);
xAxis
.scale(x)
//.domain(x.domain())
//.range(x.range())
.ticks( availableWidth / 100 )
.tickSize(-availableHeight, 0);
g.select('.x.axis')
.attr('transform', 'translate(0,' + y.range()[0] + ')');
d3.transition(g.select('.x.axis'))
.call(xAxis);
var xTicks = g.select('.x.axis').selectAll('g');
xTicks
.selectAll('line, text')
.style('opacity', 1)
xTicks.filter(function(d,i) {
return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
})
.selectAll('line, text')
.style('opacity', 0)
yAxis
.domain(y.domain())
.range(y.range())
.ticks( availableHeight / 36 )
.tickSize( -availableWidth, 0);
d3.transition(g.select('.y.axis'))
.call(yAxis);
legend.dispatch.on('legendClick', function(d,i) {
d.disabled = !d.disabled;
if (!data.filter(function(d) { return !d.disabled }).length) {
data.map(function(d) {
d.disabled = false;
wrap.selectAll('.series').classed('disabled', false);
return d;
});
}
selection.transition().call(chart);
});
controls.dispatch.on('legendClick', function(d,i) {
if (!d.disabled) return;
controlsData = controlsData.map(function(s) {
s.disabled = true;
return s;
});
d.disabled = false;
switch (d.key) {
case 'Grouped':
multibar.stacked(false);
break;
case 'Stacked':
multibar.stacked(true);
break;
}
selection.transition().call(chart);
});
multibar.dispatch.on('elementMouseover.tooltip', function(e) {
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
dispatch.tooltipShow(e);
});
multibar.dispatch.on('elementMouseout.tooltip', function(e) {
dispatch.tooltipHide(e);
});
});
return chart;
}
chart.dispatch = dispatch;
chart.legend = legend;
chart.xAxis = xAxis;
chart.yAxis = yAxis;
d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id');
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.width = function(_) {
if (!arguments.length) return width;
width = d3.functor(_);
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = d3.functor(_);
return chart;
};
return chart;
}
@ -4343,8 +4591,7 @@ nv.models.stackedAreaWithLegend = function() {
availableWidth = width - margin.left - margin.right,
availableHeight = height - margin.top - margin.bottom;
var series = data.filter(function(d) { return !d.disabled })
//.map(function(d) { return d.values });
var seriesData = data.filter(function(d) { return !d.disabled })
.reduce(function(prev, curr, index) { //sum up all the y's
curr.values.forEach(function(d,i) {
if (!index) prev[i] = {x: getX(d,i), y:0};
@ -4354,11 +4601,11 @@ nv.models.stackedAreaWithLegend = function() {
}, []);
x .domain(d3.extent(d3.merge(series), function(d) { return d.x } ))
x .domain(d3.extent(d3.merge(seriesData), function(d) { return d.x } ))
.range([0, availableWidth]);
y .domain(stacked.offset() == 'zero' ?
[0, d3.max(series, function(d) { return d.y } )] :
[0, d3.max(seriesData, function(d) { return d.y } )] :
[0, 1] // 0 - 100%
)
.range([availableHeight, 0]);

6
nv.d3.min.js vendored

File diff suppressed because one or more lines are too long

@ -11,7 +11,8 @@ nv.models.multiBar = function() {
clipEdge = true,
stacked = false,
color = d3.scale.category20().range(),
xDomain, yDomain;
xDomain, yDomain,
x0, y0;
//var x = d3.scale.linear(),
var x = d3.scale.ordinal(), //TODO: Need to figure out how to use axis model with ordinal scale
@ -42,6 +43,9 @@ nv.models.multiBar = function() {
availableWidth = width - margin.left - margin.right,
availableHeight = height - margin.top - margin.bottom;
//store old scales if they exist
x0 = x0 || x;
y0 = y0 || y;
//add series index to each data point for reference
data = data.map(function(series, i) {
@ -135,7 +139,7 @@ nv.models.multiBar = function() {
.attr('x', 0 )
//.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
//.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) })
.attr('y', y(0))
.attr('y', function(d) { return y0(stacked ? d.y0 : 0) })
.attr('height', 0)
.attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
.on('mouseover', function(d,i) {
@ -199,23 +203,14 @@ nv.models.multiBar = function() {
});
function transitionStacked() {
stacked = true;
selection.transition().call(chart);
}
function transitionGrouped() {
stacked = false;
selection.transition().call(chart);
}
window.grouped = transitionGrouped;
window.stacked = transitionStacked;
chart.update = function() {
selection.transition().call(chart);
}
//store old scales for use in transitions on update, to animate from old to new positions, and sizes
x0 = x.copy();
y0 = y.copy();
});
return chart;

@ -118,6 +118,17 @@ nv.models.multiBarWithLegend = function() {
d3.transition(g.select('.x.axis'))
.call(xAxis);
var xTicks = g.select('.x.axis').selectAll('g');
xTicks
.selectAll('line, text')
.style('opacity', 1)
xTicks.filter(function(d,i) {
return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
})
.selectAll('line, text')
.style('opacity', 0)
yAxis
.domain(y.domain())

Loading…
Cancel
Save