I am trying to implement a 'Stacked Area Chart' with d3js and nvd3.js similar to this example. Additionally, I'd like to use a context brush like this one to select a date range, which effects the Stacked Area Chart. Actually, this is already working but somehow it draws some lines on top of the Y-Axis as soon as the selected date range does not contain the first date. Just have a look on the following picture:
Here is my code:
Stacked Area Chart
var margin = {
top : 10,
right : 20,
bottom : 100,
left : 20
}, width = 960, height = 300;
var svg_stack = d3.select("#stack").append("svg").attr("width", width + margin.left + margin.right).attr("height", (height + margin.top + margin.bottom));
function initStackChart() {
nv.addGraph(function() {
var chart = nv.models.stackedAreaChart().x(function(d) {
return Date.parse(new Date(d[0]))
}).y(function(d) {
return d[1]
}).clipEdge(false);
chart.xAxis.tickFormat(function(d) {
return d3.time.format('%x')(new Date(d))
});
chart.yAxis.tickFormat(d3.format(',.2f'));
if (!!time_range) {
chart.xDomain([time_range[0], time_range[1]]);
}
d3.select('#stack svg').datum(temp_data).transition().duration(100).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
}
Brush
var margin = {top: 10, right: 20, bottom: 0, left: 20},
width = 960,
height = 50;
var contextHeight = 50;
contextWidth = width;
var parseDate = d3.time.format("%Y-%m-%d").parse;
var x = d3.time.scale().range([0, width]),
y = d3.scale.linear().range([contextHeight, 0]);
var xAxis = d3.svg.axis().scale(x).tickSize(contextHeight).tickPadding(-10).orient("bottom");
var brush = d3.svg.brush()
.x(x)
.on("brush", brushed);
var area2 = d3.svg.area()
.interpolate("monotone")
.x(function(d) { return x(d.time); })
.y0(contextHeight)
.y1(0);
var svg_brush = d3.select("#brush").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
svg_brush.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var context = svg_brush.append("g").attr("class","context")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
function initBrush(data)
{
x.domain(d3.extent(data.map(function(d) { return d.time; })));
context.append("g")
.attr("class", "x axis top")
.attr("transform", "translate(0,0)")
.call(xAxis);
context.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", 0)
.attr("height", contextHeight);
};
function brushed() {
var b = brush.empty() ? x.domain() : brush.extent();
console.log(b);
time_range=b;
initStackChart();
}
Data
var temp_data = [
{
key: "Node0",
values:
[
[ 1364795940000, 10 ],
[ 1365020480000, 30 ],
[ 1365630480000, 30 ],
[ 1366000480012, 30 ],
[ 1366012740000, 0 ]
]
},
{
key: "Node1",
values:
[
[ 1364795940000, 10 ],
[ 1365020480000, 20 ],
[ 1365630480000, 34 ],
[ 1366000480012, 82 ],
[ 1366012740000, 0 ]
]
},
{
key: "Node2",
values:
[
[ 1364795940000, 20 ],
[ 1365020480000, 10 ],
[ 1365630480000, 0 ],
[ 1366000480012, 100 ],
[ 1366012740000, 80 ]
]
},
{
key: "Node3",
values:
[
[ 1364795940000, 10 ],
[ 1365020480000, 60 ],
[ 1365630480000, 10 ],
[ 1366000480012, 10 ],
[ 1366012740000, 10 ]
]
},
{
key: "Node4",
values:
[
[ 1364795940000, 16 ],
[ 1365020480000, 32 ],
[ 1365630480000, 10 ],
[ 1366000480012, 90 ],
[ 1366012740000, 10 ]
]
},
{
key: "Node5",
values:
[
[ 1364795940000, 10 ],
[ 1365020480000, 50 ],
[ 1365630480000, 10 ],
[ 1366000480012, 20 ],
[ 1366012740000, 110 ]
]
},
{
key: "Node6",
values:
[
[ 1364795940000, 19 ],
[ 1365020480000, 55 ],
[ 1365630480000, 32 ],
[ 1366000480012, 12 ],
[ 1366012740000, 12 ]
]
},
{
key: "Node7",
values:
[
[ 1364795940000, 0 ],
[ 1365020480000, 20 ],
[ 1365630480000, 40 ],
[ 1366000480012, 30 ],
[ 1366012740000, 20 ]
]
},
{
key: "Node8",
values:
[
[ 1364795940000, 12 ],
[ 1365020480000, 31 ],
[ 1365630480000, 40 ],
[ 1366000480012, 20 ],
[ 1366012740000, 15 ]
]
},
{
key: "Node9",
values:
[
[ 1364795940000, 10 ],
[ 1365020480000, 35 ],
[ 1365630480000, 50 ],
[ 1366000480012, 30 ],
[ 1366012740000, 90 ]
]
}
]
Thank you.
Change .clipEdge(false);
to .clipEdge(true);
in your chart settings.
Okay, I've managed to recreate your problem on the NVD3 live code site with the following code (data and markup the same as their stacked graph example):
nv.addGraph(function() {
var chart = nv.models.stackedAreaChart()
.x(function(d) { return d[0] })
.y(function(d) { return d[1] })
.clipEdge(true);
var chart2 = nv.models.stackedAreaChart()
.x(function(d) { return d[0] })
.y(function(d) { return d[1] })
.xDomain([1096516800000, 1270008000000])
.clipEdge(true);
chart.xAxis
.showMaxMin(false)
.tickFormat(function(d) { return d3.time.format('%x')(new Date(d)) });
chart.yAxis
.tickFormat(d3.format(',.2f'));
chart2.xAxis
.showMaxMin(false)
.tickFormat(function(d) { return d3.time.format('%x')(new Date(d)) });
chart2.yAxis
.tickFormat(d3.format(',.2f'));
d3.select('#chart svg')
.datum(data)
.transition().duration(500).call(chart)
.transition().delay(3000).duration(500)
.call(chart2);
nv.utils.windowResize(chart.update);
return chart2;
});
Which is basically what you are doing -- creating a completely new chart function, and calling it on the same container. The chart function mostly selects all the same objects, and changes their attributes -- resulting in the smooth transition. But, the random id code it gives to the <clipPath>
element (to ensure that each element has a unique id) no longer matches up with the one it uses as the "clip-path" attribute. You could call this a bug in the NVD3 code, but it is also partly because you are using the function in unexpected ways.
In contrast, if I use this code:
nv.addGraph(function() {
var chart = nv.models.stackedAreaChart()
.x(function(d) { return d[0] })
.y(function(d) { return d[1] })
.clipEdge(true);
chart.xAxis
.showMaxMin(false)
.tickFormat(function(d) { return d3.time.format('%x')(new Date(d)) });
chart.yAxis
.tickFormat(d3.format(',.2f'));
var svg = d3.select('#chart svg')
.datum(data)
.transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
var change = window.setTimeout(function(){
chart.xDomain([1096516800000, 1270008000000]);
chart.update();
}, 3000);
return chart;
});
The clipping paths still work nicely. Notice the difference? Instead of creating and calling an entire new chart function, I have just updated the chart function with the new domain, and called the function's update()
method. Try re-arranging your brushing function to do the update that way, and not only should you fix your clipping path problem, but your code should be faster as well.
So how to implement this with your original code?
First, you need to save the chart-function object created within nv.addGraph()
into a variable that can be accessed by your brushed()
function.
Then, in your brushed()
function, you modify your saved chart-function to apply the new x-domain, and then call the function object's update method.
var margin = {
top : 10,
right : 20,
bottom : 100,
left : 20
}, width = 960, height = 300;
var chart; // NEW! declare a variable that can be accessed by both
// initialization and update functions
var svg_stack = d3.select("#stack")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", (height + margin.top + margin.bottom));
function initStackChart() {
nv.addGraph(function() {
chart = nv.models.stackedAreaChart()
// NEW! no "var" statement!
// this gets assigned to the chart variable declared above
/* rest of chart initialization code, the same as before */
});
}
/* All the initialization code for the timeline brushing goes here, until: */
function brushed() {
var b = brush.empty() ? x.domain() : brush.extent();
console.log(b);
time_range=b;
chart.xDomain(b); //modify the saved chart object
chart.update(); //update the chart using the saved function
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With