I have a project where I need to zoom on 3 axes independently. I'm using scroll bars to accomplish this, but the axes aren't redrawing when the zoom happens.
Additionally, the clip path doesn't seem to be working as expected.
Eventually I want to add panning functionality to the chart as well, but I'm not sure where to start for this.
http://jsfiddle.net/yo4mwLpj/
Thanks in advance for the help.
// cases vs deaths of a disease over time
var data = [
{"year": "1960", "cases":"887", "deaths": "199"},
{"year": "1965", "cases":"218", "deaths": "55"},
{"year": "1993", "cases":"37046", "deaths": "931"},
{"year": "1994", "cases":"38735", "deaths": "118"},
{"year": "1995", "cases":"19903", "deaths": "624"},
{"year": "1997", "cases":"4170", "deaths": "125"},
{"year": "1998", "cases":"10000", "deaths": "0"}
];
data.forEach(function (d) {
d.year = d3.time.format("%Y").parse(d.year.toString());
d.cases = +d.cases;
d.deaths = +d.deaths;
});
var margin = { top: 30, right: 40, bottom: 30, left: 50 },
width = 500 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var xScale = d3.time.scale()
.domain(d3.extent(data, function (d) { return d.year; }))
.range([0, width]);
var yScaleLeft = d3.scale.linear()
.domain([0, d3.max(data, function (d) { return d.cases; })])
.range([height, 0]);
var yScaleRight = d3.scale.linear()
.domain([0, d3.max(data, function (d) { return d.deaths; })])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom").ticks(5);
var yAxisLeft = d3.svg.axis()
.scale(yScaleLeft)
.orient("left").ticks(5);
var yAxisRight = d3.svg.axis()
.scale(yScaleRight)
.orient("right").ticks(5);
var lineCases = d3.svg.line()
.x(function (d) { return xScale(d.year); })
.y(function (d) { return yScaleLeft(d.cases); });
var lineDeaths = d3.svg.line()
.x(function (d) { return xScale(d.year); })
.y(function (d) { return yScaleRight(d.deaths); });
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis yleft")
.call(yAxisLeft);
svg.append("g")
.attr("class", "y axis yright")
.call(yAxisRight)
.attr('transform', 'translate(' + width + ',0)');
svg.append("path")
.datum(data)
.attr("class", "line lineLeft")
.style("stroke", "red")
.attr("d", lineCases(data))
.attr("clip", "url(#clip)");
svg.append("path")
.datum(data)
.attr("class", "line lineRight")
.attr("d", lineDeaths(data))
.attr("clip", "url(#clip)");
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
$("#slider-x").slider({
orientation: "horizontal",
range: "min",
min: 1000,
max: 10000, // make max be (maxDate-minDate).Days*1000, so you can zoom to one day
value: 1000,
slide: function( event, ui ) {
zoomXWithSlider(ui.value/1000);
}
});
$( "#slider-y-left" ).slider({
orientation: "vertical",
range: "min",
min: 1000,
max: 10000,
value: 1000,
slide: function( event, ui ) {
zoomLeftWithSlider(ui.value/1000);
}
});
$("#slider-y-right").slider({
orientation: "vertical",
range: "min",
min: 1000,
max: 10000,
value: 1000,
slide: function( event, ui ) {
zoomRightWithSlider(ui.value/1000);
}
});
function zoomXWithSlider(scale) {
// Note: works only on the <g> element and not on the <svg> element
// which is a common mistake
svg.selectAll("path.line").attr("transform", "scale("+scale+", 1)");
svg.select(".x.axis").call(xAxis);
}
function zoomLeftWithSlider(scale) {
svg.select("path.line.lineLeft").attr("transform", "scale(1, "+scale+")");
svg.select(".y.axis.yleft").call(yAxisLeft);
}
function zoomRightWithSlider(scale) {
svg.select("path.line.lineRight").attr("transform", "scale(1, "+scale+")");
svg.select(".y.axis.yright").call(yAxisRight);
}
http://jsfiddle.net/8oo3ocfs/
var zoom = d3.behavior.zoom() //zoomYAxis
.x(xScale)
.y(yScaleLeft)
.on("zoom", function(){
// don't let double-click or scroll wheel do anything
if (d3.event.sourceEvent == null || d3.event.sourceEvent.type == "wheel"){
zoom.scale(previousScale);
zoom.translate(previousTranslate);
zoomRight.scale(previousScale);
zoomRight.translate(previousTranslate);
return;
}
// set previous scale for future use
previousScale = zoom.scale();
previousTranslate = zoom.translate();
//zoom.translate(panLimit());
//zoomRight.translate(zoom.translate());
// update the right side scale
zoomRight.scale(previousScale);
zoomRight.translate(previousTranslate);
// redraw lines
svg.select("path.line.lineLeft").attr("d", lineCases(data));
svg.select("path.line.lineRight").attr("d", lineDeaths(data));
// redraw axes
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis.yleft").call(yAxisLeft);
svg.select(".y.axis.yright").call(yAxisRight);
});
I eventually got this working. I don't need the clip-path to work in the jsfiddle because it works in our actual project. Additionally, I didn't end up limiting the pan functionality because it got very complex with the math. So I just put a reset button on it.
Related
I need to add dots to the stacked area chart in the code below. I've tried numerous iterations in the code with .data(layers), but it errors out when looking for cx and cy on all the iterations I've tried. The main issue is that i don't understand how to drill into the layers variable to get the cummulative summation so the circles match the lines in the stacked area.
Here's a fiddle, and heres a snippet:
var data = d3.csv.parse(d3.select("#dataset").text());
d3.select("#dataset").remove();
var format = d3.time.format("%m/%d/%y");
var margin = {top: 20, right: 30, bottom: 30, left: 40},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var z = d3.scale.category20c();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.days);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var stack = d3.layout.stack()
.offset("zero")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
var nest = d3.nest()
.key(function(d) { return d.key; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var mygroups = d3.map(data, function(d){return(d.key)}).keys()
var color = d3.scale.ordinal()
.domain(mygroups)
.range(['#CA999A','#99A3B0','#9FBD9F'])
data.forEach(function(d) {
d.date = format.parse(d.date);
d.value = +d.value;
});
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter()
.append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return color(i); })
.style("stroke","black");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("path","line")
.style({
fill: "none",
stroke: "#000",
"shape-rendering":"crispEdges"
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll("path","line")
.style({
fill: "none",
stroke: "#000",
"shape-rendering":"crispEdges"
});
//adds dots where original data would go but without error
/* svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("r", 5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.value); });
*/
//errors out with <circle> attribute cx: Expected length, "NaN".
//Tired various looping functions that would not work.
//I want the dots to follow the lines in the stack.
// it does at least put a dot on the graph
svg.selectAll("circle")
.data(layers)
.enter()
.append("circle")
.attr("class", "dot")
.attr("r", 5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.value); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<pre id = "dataset">key,value,date
Group1,37,04/23/12
Group2,12,04/23/12
Group3,46,04/23/12
Group1,32,04/24/12
Group2,19,04/24/12
Group3,42,04/24/12
Group1,45,04/25/12
Group2,16,04/25/12
Group3,44,04/25/12
Group1,24,04/26/12
Group2,52,04/26/12
Group3,64,04/26/12</pre>
⚠️This question and answer both use d3v3 - d3v4+ stacks create a data array with a different structure - as such this answer may not be useful for d3v4+
Plotting circles with the data variable won't work, even if scaling, as the values returned will be for a non-cumulative line/area graph. Alternatively, plotting values in the layers variable won't work as it contains only one value per layer. Each layer in layers contains an array with all the points that belong to that layer, we can grab all of those points and plot those as circles.
Here's the layers structure:
[
{
"key": "Group1",
"values": [
{
"key": "Group1",
"value": 37,
"date": "2012-04-23T07:00:00.000Z",
"y0": 0,
"y": 37
},
...
{
"key": "Group1",
"value": 24,
"date": "2012-04-26T07:00:00.000Z",
"y0": 0,
"y": 24
}
]
},
{
"key": "Group2",
"values": [
{
"key": "Group2",
"value": 12,
"date": "2012-04-23T07:00:00.000Z",
"y0": 37,
"y": 12
},
... // an so on.
So, we can cycle through each item of layers, which means we cycle through each layer, and collect all the points. Something like this will suffice:
// Grab all coordinates of all layers:
var points = [];
layers.forEach(function(d) {
return points.push(...d.values);
})
Now, let's look at each of the items in our points array:
{
"key": "Group1",
"value": 37,
"date": "2012-04-23T07:00:00.000Z",
"y0": 0,
"y": 37
}
Here, y represents an items height, and y0 represents it's base (the bottom of the layer at that point). Since we only want to plot each point once, and we don't want to plot the 0 values of the bottom layer's y0 property, we should only plot the topmost y value (y+y0):
//adds dots where original data would go but without error
svg.selectAll("circle")
.data(points)
.enter()
.append("circle")
.attr("class", "dot")
.attr("r", 5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.y0+d.y); });
Which looks like this altogether:
var data = d3.csv.parse(d3.select("#dataset").text());
d3.select("#dataset").remove();
var format = d3.time.format("%m/%d/%y");
var margin = {top: 20, right: 30, bottom: 30, left: 40},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var z = d3.scale.category20c();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.days);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var stack = d3.layout.stack()
.offset("zero")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
var nest = d3.nest()
.key(function(d) { return d.key; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var mygroups = d3.map(data, function(d){return(d.key)}).keys()
var color = d3.scale.ordinal()
.domain(mygroups)
.range(['#CA999A','#99A3B0','#9FBD9F'])
data.forEach(function(d) {
d.date = format.parse(d.date);
d.value = +d.value;
});
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter()
.append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return color(i); })
.style("stroke","black");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("path","line")
.style({
fill: "none",
stroke: "#000",
"shape-rendering":"crispEdges"
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll("path","line")
.style({
fill: "none",
stroke: "#000",
"shape-rendering":"crispEdges"
});
// Grab all coordinates of all layers:
var points = [];
layers.forEach(function(d) {
return points.push(...d.values);
})
//adds dots where original data would go but without error
svg.selectAll("circle")
.data(points)
.enter()
.append("circle")
.attr("class", "dot")
.attr("r", 5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.y0+d.y); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<pre id = "dataset">key,value,date
Group1,37,04/23/12
Group2,12,04/23/12
Group3,46,04/23/12
Group1,32,04/24/12
Group2,19,04/24/12
Group3,42,04/24/12
Group1,45,04/25/12
Group2,16,04/25/12
Group3,44,04/25/12
Group1,24,04/26/12
Group2,52,04/26/12
Group3,64,04/26/12</pre>
I want to show the axisBottom value as an integer, with nothing in between the whole numbers. In the below image I use tickFormat which switched the values to integers, but now I need to only have the values of '1, 2, 3..etc" rather than the duplicate integer values. I need the number of ticks to be dynamically generated, meaning I can't statically say there are 3 ticks. The data I pass may have a max value of 3 or any other number, but they will all be whole numbers.
Data (JSON)
[ { yAxis: '15.1.1', xAxis: 2 },
{ yAxis: '15.1.2', xAxis: 2 },
{ yAxis: '15.1.3', xAxis: 1 },
{ yAxis: '15.1.4', xAxis: 3 },
{ yAxis: '15.1.5', xAxis: 0 },
{ yAxis: '15.1.6', xAxis: 1 },
{ yAxis: '15.1.7', xAxis: 0 },
{ yAxis: '15.1.8', xAxis: 0 } ]
Images and code below.
var data = !{dataObj}; //using Jade as template engine
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 80},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// set the ranges
var y = d3.scaleBand()
.range([height, 0])
.padding(0.4);
var x = d3.scaleLinear()
.range([0, width]);
var svg = d3.select(".barChartContainer").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// format the data
data.forEach(function(d) {
d.xAxis = +d.xAxis;
});
// Scale the range of the data in the domains
x.domain([0, d3.max(data, function(d){ return d.xAxis; })])
y.domain(data.map(function(d) { return d.yAxis; }));
//y.domain([0, d3.max(data, function(d) { return d.prereqs; })]);
// append the rectangles for the bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
//.attr("x", function(d) { return x(d.prereqs); })
.attr("width", function(d) {return x(d.xAxis); } )
.attr("y", function(d) { return y(d.yAxis); })
.attr("height", y.bandwidth());
// add the x Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the y Axis
svg.append("g")
.call(d3.axisLeft(y));
You have two options here.
The first one is showing only the integers, but keeping the ticks. This can be done testing if the number is an integer inside tickFormat:
.tickFormat(function(d) {
return d % 1 ? null : d;
});
Here is a demo:
var svg = d3.select("svg");
var scale = d3.scaleLinear()
.range([20, 480])
.domain([0, 3]);
var axis = d3.axisBottom(scale)
.tickFormat(function(d) {
return d % 1 ? null : d;
});
var gX = svg.append("g")
.attr("transform", "translate(0, 50)")
.call(axis);
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="100"></svg>
However, if you want to show only the ticks for the integers, the solution is using tickValues:
.tickValues(d3.range(scale.domain()[0], scale.domain()[1] + 1, 1))
Here is a demo:
var svg = d3.select("svg");
var scale = d3.scaleLinear()
.range([20, 480])
.domain([0, 3]);
var axis = d3.axisBottom(scale)
.tickValues(d3.range(scale.domain()[0], scale.domain()[1] + 1, 1))
.tickFormat(function(d) {
return ~~d;
});
var gX = svg.append("g")
.attr("transform", "translate(0, 50)")
.call(axis);
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="100"></svg>
I am new to D3 JS and looking for a customize solution which is not available out of the box in d3 JS.
Below code produced a bar chart which denotes no. of students against 3 different classes,
Question, Can I show Circle instead of bar? please suggest some code? Thanks!
//data
let data = [{ "noOfStudents": 30, "ClassName": "Class 1" }, { "noOfStudents": 42, "ClassName": "Class 2" }, { "noOfStudents": 38, "ClassName": "Class 3" }];
// set the dimensions and margins of the graph
var margin = { top: 20, right: 20, bottom: 30, left: 40 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleBand().range([0, width]).padding(0.1);
var y = d3.scaleLinear().range([height, 0]);
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// get and format the data
data.forEach(function (d) {
d.noOfStudents = +d.noOfStudents;
});
// Scale the range of the data in the domains
x.domain(data.map(function (d) { return d.ClassName; }));
y.domain([0, d3.max(data, function (d) { return d.noOfStudents; })]);
// append the rectangles for the bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) { return x(d.ClassName); })
.attr("width", x.bandwidth())
.attr("y", function (d) { return y(d.noOfStudents); })
.attr("height", function (d) { return height - y(d.noOfStudents); })
.text(function (d) { return d.noOfStudents; });
// add the x Axis
svg.append("g").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(x));
// add the y Axis
svg.append("g").call(d3.axisLeft(y));
Instead of rectangles, just append circles:
svg.selectAll(".bar")
.data(data)
.enter().append("circle")
.attr("class", "bar")
.attr("cx", function (d) { return x(d.ClassName); })
.attr("cy", function (d) { return y(d.noOfStudents); })
.attr("r", 30)
.text(function (d) { return d.noOfStudents; });
And change your band scale for a point scale:
var x = d3.scalePoint()
.range([0, width])
.padding(0.4);
Here is a fiddle: https://jsfiddle.net/kks4gcL3/
I’m starting with d3.js and built a simple stacked chart.
Now I want to be able to update the chart with new dataset on click.
I followed tutorials and especially the Thinking with Joins article, the General Update Pattern example and this stackoverflow question but I didn’t manage to apply the enter/update/exit logic to my example.
As you can see in this fiddle, the updated axis are placed on top of the previous one and the chart doesn’t update with the new data.
var data1 = [
{month: 'Jan', A: 20, B: 5, C: 10},
{month: 'Feb', A: 30, B: 10, C: 20}
];
var data2 = [
{month: 'Mar', A: 10, B: 55, C: 100},
{month: 'Apr', A: 10, B: 70, C: 2}
];
var xData = ["A", "B", "C"];
var margin = {top: 20, right: 50, bottom: 30, left: 50},
width = 400 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], 0.35);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var color = d3.scale.category20();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
function draw(data) {
var dataIntermediate = xData.map(function (c) {
return data.map(function (d) {
return {x: d.month, y: d[c]};
});
});
var dataStackLayout = d3.layout.stack()(dataIntermediate);
x.domain(dataStackLayout[0].map(function (d) {
return d.x;
}));
y.domain([0,
d3.max(dataStackLayout[dataStackLayout.length - 1],
function (d) { return d.y0 + d.y;})
])
.nice();
var layer = svg.selectAll(".stack")
.data(dataStackLayout);
layer.exit().remove(); // has no effect
layer.enter().append("g")
.attr("class", "stack")
.style("fill", function (d, i) {
return color(i);
});
var rect = layer.selectAll("rect")
.data(function (d) {
return d;
});
rect.exit().remove(); // has no effect
rect.enter().append("rect")
.attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) {
return y(d.y + d.y0);
})
.attr("height", function (d) {
return y(d.y0) - y(d.y + d.y0);
})
.attr("width", x.rangeBand());
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis")
.call(yAxis);
}
function updateData() {
draw(data2);
}
d3.select('#update')
.on("click", updateData);
draw(data1);
Could you please explain where to insert the exit there and why?
Many thanks
To summarize the enter/update/exit logic;
When you first create your svg, the enter part takes the new nodes and appends them to the svg.
When you update your svg, the select part updates your data and styles. And the exit part removes the data as you know.
So according to this your pattern is almost correct but when you select your new data(update), you are not updating your styles.
Here's the part you need to change:
var rect = layer.selectAll("rect")
.data(function (d) {
return d;
}).attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) {
return y(d.y + d.y0);
})
.attr("height", function (d) {
return y(d.y0) - y(d.y + d.y0);
})
.attr("width", x.rangeBand());
And here's the updated fiddle: https://jsfiddle.net/8gp8x89c/2/
Note that the axis' are still present so you either remove and re-append them or apply the update pattern to them also. I leave that part to you.
What would be causing this chart to have a value appear below the x-axis (0:00)?
My data looks like this (no values less than zero):
[{"x":1341806400,"y":4},
{"x":1342411200,"y":0},
{"x":1343016000,"y":0},
{"x":1343620800,"y":7},
{"x":1344225600,"y":6},
{"x":1344830400,"y":73},
{"x":1345435200,"y":328},
{"x":1346040000,"y":0},
{"x":1346644800,"y":0},
{"x":1347249600,"y":0},
{"x":1347854400,"y":0},
{"x":1348459200,"y":11},
{"x":1349064000,"y":17},
{"x":1349668800,"y":0},
{"x":1350273600,"y":0}]
Rendered Chart
The above chart is rendered via:
var margin = {top: 20, right: 30, bottom: 30, left: 40};
var width = max_width - margin.left - margin.right;
var height = 300; // + margin.top + margin.bottom;
var height_offset = 100;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var z = d3.scale.category20c();
var ticks_interval;
switch(this.period_type){
case "day":
ticks_interval = d3.time.days;
break;
case "week":
ticks_interval = d3.time.weeks;
break;
case "month":
ticks_interval = d3.time.months;
break;
case "year":
ticks_interval = d3.time.years;
break;
}
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(ticks_interval);
var yAxis = d3.svg.axis()
.scale(y)
.tickFormat(function(d){
return numeral(d).format("00:00").replace(/^0:/,""); })
.orient("left");
var stack = d3.layout.stack()
.offset("zero")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
var nest = d3.nest()
.key(function(d) { return d.key; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y0(function(d) {return y(d.y0);})
.y1(function(d) { return y(d.y0 + d.y); });
var svg = d3.select(chart_dom_el)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + height_offset)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d){ return d.date; }));
y.domain([0,d3.max(data,function(d){
return d.y0+ d.y;
})]);
// re-map for formatted date
data = _.map(data,function(d){
return {date: d.date.format("MM/D"),value:d.value};
});
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return z(i); });
var x_axis_options = {x: 4, y: 9, rotate: 0};
if(data.length > 20){
x_axis_options = {x: -27, y: 8, rotate: -45};
}else if(data.length > 13){
x_axis_options = {y: -5, x: 27, rotate: 90};
}
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("y", x_axis_options.y)
.attr("x", x_axis_options.x)
.attr("transform", "rotate("+(x_axis_options.rotate)+")");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
This appears to have to do with the interpolation mode you used on the area generator - try changing from cardinal to linear, or some other area interpolation mode