I have constructed a stacked bar chart with approx 700 bars. Everything function as it should but I am getting really frustrated with the stripes that appear when the chart is drawn. Below is a screenshot with the default view and a zoomed view.
zoomed view to the left, default to the right
I suspect that the stripes come from the padding between the bars. I've tampered with the bar width to try and eliminate the padding but the stripes are still there. Currently the bar width code looks like this:
.attr("width",((width-(padding+xPadding))/data.length)+0.01)
The "+0.01" removes the padding and if I increase it further to, say 1, the stripes are gone. However, now the bars are stacked on each other noticably, which I do not want. I suspect there is some quick fix to this(maybe css or something other trivial) but I cannot find it myself. So, how do I solve this?
Thanks in advance.
EDIT 1:
Tried using scalebands as suggested in comments but it had no effect on the stripes.
same behaviour with scalebands
EDIT 2:
Added relevant code used to draw rectangles. Note the code does not run, snippet is just for viewing the code.
d3.csv("vis_temp.csv", function(d, i, columns) {
for (i = 1, t = 0; i < columns.length-1; ++i){ //calculate total values. ignore last column(usecase)
t += d[columns[i]] = +d[columns[i]];
}
d.total = t;
return d;
}, function(error,data){
if(error){
console.log(error);
return;
}
console.log(data);
dataset = data; // save data outside of d3.csv function
header = data.columns.slice(1); //prop1, prop2..... no sample
header.splice(header.length-1,1); //remove usecase from header
stack = d3.stack().keys(header);
maxValue = d3.max(data,function(d){
return d.total;});
samples = data.map(function(d){
return d.sample;});
xScale = d3.scaleLinear()
.domain([1,samples.length+1])
.range([padding+1,width-xPadding]);
/* using scalebands
xScale = d3.scaleBand()
.domain(d3.range(data.length))
.range([padding+1,width-xPadding]);
*/
yScale = d3.scaleLinear()
.domain([0,maxValue])
.range([height-padding,padding]);
zScale = d3.scaleOrdinal()
.domain(header)
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); // low profile, stylish colors
xAxis = d3.axisBottom()
.scale(xScale)
.ticks(nbrOfXTicks);
yAxis = d3.axisLeft()
.scale(yScale)
.ticks(nbrOfYTicks);
svg.append("text")
.attr("class","chart_item")
.attr("x",(width-padding-xPadding-20)/2)
.attr("y",padding/2)
.text("measurement");
svg.append("text")
.attr("class","chart_item")
.attr("x",padding/3)
.attr("y",height/2)
.attr("transform","rotate(270,"+padding/3+","+height/2+")")
.text("Time [ms]")
svg.append("text")
.attr("class","chart_item")
.attr("x",(width-padding-xPadding)/2)
.attr("y",height-7)
.text("Sample");
svg.append("g")
.attr("class","axis")
.attr("id","x_axis")
.attr("transform","translate(0,"+(height-padding)+")")
.call(xAxis);
svg.append("g")
.attr("class","axis")
.attr("id","y_axis")
.attr("transform","translate("+padding+",0)")
.call(yAxis);
svg.append("g").attr("class","data");
svg.select(".data")
.selectAll("g")
.data(stack(data))
.enter()
.append("g")
.attr("class","data_entry")
.attr("id",function(d){
return d.key;})
.attr("fill",function(d){
return zScale(d.key);})
.selectAll("rect")
.data(function(d,i){
return d;})
.enter()
.append("rect")
.attr("id",function(d){
return "bar_"+d.data.sample;})
.style("opacity",function(d){
return d.data.usecase=="E" ? val1 : val2;})//some bars opacity change
.attr("width",((width-(padding+xPadding))/data.length)+0.01) // +0.01 to remove whitespace between bars
//.attr("width",xScale.bandwidth()) use this with scalebands
.attr("height",function(d){
return (yScale(d[0])-(yScale(d[1])));
})
.attr("x",function(d){
return xScale(d.data.sample);})
.attr("y",function(d){
return yScale(d[1]);})
.on("mouseover",mouseover) //tooltip on mouseover
.on("mouseout", function() {
d3.select("#tooltip").classed("hidden", true);
});
When using ordinal scale for x axis, you can set the bar padding in the range.
For example:
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeBands([0, width], 'padding');
A regular padding value would be around 0.1, but you can set to 0 since you don't want padding.
Now, you can set your width attr like this: .attr("width", xScale.rangeBand())
Related
I have been able to make a scatter plot with zoom and pan functionality where the axes scale properly and everything works well. Now I am trying to figure out how to add gridlines, but running into some issues. I have started with only adding x-axis gridlines to figure things out. I have attached a fiddle with a working example to build from.
I commented out the initial gridlines when the graph is generated, because they would remain after zooming causing clutter, and I will add them back later when I get things working. When zooming the gridlines appear to be drawn correctly, but they do not match up with the x-axis labels, and the x-axis labels disappear after zooming or panning.
If you comment out line 163 and uncomment line 164 you can see the basic graph without any gridlines. Clicking the plot button will always generate a new graph. I have left behind some commented out code of different things that I have tried from searching through stackoverflow.
Example is using d3.js - 5.9.2
JSFiddle: https://jsfiddle.net/eysLvqkh/11/
HTML:
<div id="reg_plot"></div>
<button id="b" class="myButton">plot</button>
Javascript:
var theButton = document.getElementById("b");
theButton.onclick = createSvg;
function createSvg() {
// clear old chart when 'plot' is clicked
document.getElementById('reg_plot').innerHTML = ""
// dimensions
var margin = {top: 20, right: 20, bottom: 30, left: 55},
svg_dx = 1200,
svg_dy =600,
chart_dx = svg_dx - margin.right - margin.left,
chart_dy = svg_dy - margin.top - margin.bottom;
// data
var y = d3.randomNormal(400, 100);
var x_jitter = d3.randomUniform(-100, 1400);
var d = d3.range(1000)
.map(function() {
return [x_jitter(), y()];
});
// fill
var colorScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([0, 1]);
// y position
var yScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([chart_dy, margin.top]);
// x position
var xScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[0]; }))
.range([margin.right, chart_dx]);
// y-axis
var yAxis = d3.axisLeft(yScale);
// x-axis
var xAxis = d3.axisBottom(xScale);
// append svg to div element 'reg_plot' and set zoom to our function named 'zoom'
var svg = d3.select("#reg_plot")
.append("svg")
.attr("width", svg_dx)
.attr("height", svg_dy);
svg.call(d3.zoom().on("zoom", zoom));
// clip path - sets boundaries so points will not show outside of the axes when zooming/panning
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr('width', chart_dx)
.attr('height', chart_dy);
// plot data
var circles = svg.append("g")
.attr("id", "circles")
.attr("transform", "translate(75, 0)")
.attr("clip-path", "url(#clip)")
.selectAll("circle")
.data(d)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d) { return xScale(d[0]); })
.attr("cy", function(d) { return yScale(d[1]); })
.style("fill", function(d) {
var norm_color = colorScale(d[1]);
return d3.interpolateInferno(norm_color)
});
// add y-axis
var y_axis = svg.append("g")
.attr("id", "y_axis")
.attr("transform", "translate(75,0)")
.call(yAxis).style("font-size", "10px")
// add x-axis
var x_axis = svg.append("g")
.attr("id", "x_axis")
.attr("transform", `translate(${margin.left}, ${svg_dy - margin.bottom - margin.top})`)
.call(xAxis).style("font-size", "10px")
// add x and y grid lines
x_axis.call(xAxis.scale(xScale).ticks(20).tickSize(-chart_dy));
y_axis.call(yAxis.scale(yScale).ticks(20).tickSize(-chart_dx));
function zoom(e) {
// re-scale y axis during zoom
y_axis.transition()
.duration(50)
.call(yAxis.scale(d3.event.transform.rescaleY(yScale)));
// re-scale x axis during zoom
x_axis.transition()
.duration(50)
.call(xAxis.scale(d3.event.transform.rescaleX(xScale)));
// re-draw circles using new scales
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);
// re-scale axes and gridlines
x_axis.call(xAxis.scale(new_xScale).ticks(20).tickSize(-chart_dy));
y_axis.call(yAxis.scale(new_yScale).ticks(20).tickSize(-chart_dx));
circles.data(d)
.attr('cx', function(d) {return new_xScale(d[0])})
.attr('cy', function(d) {return new_yScale(d[1])});
}
}
For anyone looking, I have solved this problem. I have updated the javascript in the original post, and updated the jsfiddle. If you are copying this code to your local machine where you are using d3.js 7.4.4 or higher then you need to change the lines that say d3.event.transform.... to just e.transform.
I would like to place images between the axis labels and the axis line or the bars in my case. Now it's a bit tricky because I don't have much space. I am restricted by the graph size and I have to work with the current dimensions. I tried the option of adding tickPadding() to the y-axis but that meant I went over the graph size and the labels were cut-off. is there a way I could move the bars to the right? or make the width a bit smaller?
here is my code for the y-axis and the bars:
let yScale_h = d3.scaleBand()
.range([0, height])
.padding(0.2);
let xScale_h = d3.scaleLinear()
.range([0, width]);
let yAxis = d3.axisLeft()
.scale(yScale_h)
.tickSize(0);
svg_bar.selectAll('rect')
.data(dataset_performance, key)
.enter()
.append('rect')
.attr("class", "bar")
.attr('width', function (d) { return xScale_h(d.Award); })
.attr('y', function (d) { return yScale_h(d.clean_test); })
.attr('height', yScale_h.bandwidth())
One way to manually offset the bars to the right is to reduce the scale range, and add the padding to the 'x' property of the bars.
This example adds a padding of 20px:
let xScale_h = d3.scaleLinear()
.range([0, width - 20]); // Reduce the range by 20px
...
svg_bar.selectAll('rect')
.data(dataset_performance, key)
.enter()
.append('rect')
.attr("class", "bar")
.attr('x', 20) // Move bars to the right by 20px
.attr('width', function (d) { return xScale_h(d.Award); })
.attr('y', function (d) { return yScale_h(d.clean_test); })
.attr('height', yScale_h.bandwidth())
I am trying to get brushing to work similar to this example, but with a grouped bar chart: http://bl.ocks.org/mbostock/1667367
I don't really have a good understanding of how brushing works (I haven't been able to find any good tutorials), so I'm a bit at a loss as to what is going wrong. I will try to include the relevant bits of code below. The chart is tracking the time to fix broken builds by day and then grouped by portfolio. So far the brush is created and the user can move and drag it, but the bars in the main chart are re-drawn oddly and the x axis is not updated at all. Any help you can give would be greatly appreciated. Thank you.
// x0 is the time scale on the X axis
var main_x0 = d3.scale.ordinal().rangeRoundBands([0, main_width-275], 0.2);
var mini_x0 = d3.scale.ordinal().rangeRoundBands([0, main_width-275], 0.2);
// x1 is the portfolio scale on the X axis
var main_x1 = d3.scale.ordinal();
var mini_x1 = d3.scale.ordinal();
// Define the X axis
var main_xAxis = d3.svg.axis()
.scale(main_x0)
.tickFormat(dateFormat)
.orient("bottom");
var mini_xAxis = d3.svg.axis()
.scale(mini_x0)
.tickFormat(dateFormat)
.orient("bottom");
After binding the data...
// define the axis domains
main_x0.domain(data.result.map( function(d) { return d.date; } )
.sort(d3.ascending));
mini_x0.domain(data.result.map( function(d) { return d.date; } )
.sort(d3.ascending));
main_x1.domain(data.result.map( function(d) { return d.portfolio; } )
.sort(d3.ascending))
.rangeRoundBands([0, main_x0.rangeBand() ], 0);
mini_x1.domain(data.result.map( function(d) { return d.portfolio; } )
.sort(d3.ascending))
.rangeRoundBands([0, main_x0.rangeBand() ], 0);
// Create brush for mini graph
var brush = d3.svg.brush()
.x(mini_x0)
.on("brush", brushed);
After adding the axis's, etc.
// Create the bars
var bar = main.selectAll(".bars")
.data(nested)
.enter().append("g")
.attr("class", function(d) { return d.key + "-group bar"; })
.attr("fill", function(d) { return color(d.key); } );
bar.selectAll("rect").append("rect")
.data(function(d) { return d.values; })
.enter().append("rect")
.attr("class", function(d) { return d.portfolio; })
.attr("transform", function(d) { return "translate(" + main_x0(d.date) + ",0)"; })
.attr("width", function(d) { return main_x1.rangeBand(); })
.attr("x", function(d) { return main_x1(d.portfolio); })
.attr("y", function(d) { return main_y(d.buildFixTime); })
.attr("height", function(d) { return main_height - main_y(d.buildFixTime); });
Here is the brush function (trying several different options)...
function brushed() {
main_x1.domain(brush.empty() ? mini_x1.domain() : brush.extent());
//main.select("rect")
//.attr("x", function(d) { return d.values; })
//.attr("width", function(d) { return d.values; });
bar.select("rect")
.attr("width", function(d) { return main_x1.rangeBand(); })
.attr("x", function(d) { return main_x1(d.portfolio); });
//.attr("y", function(d) { console.log(d); return main_y(d.buildFixTime); })
//.attr("height", function(d) { return main_height - main_y(d.buildFixTime); });
main.select(".x.axis").call(main_xAxis);
}
The problem comes from trying to use the brush to set the x-scale domain, when your x-scale is an ordinal scale. In other words, the expected domain of your x-axis is a list of categories, not a max-min numerical extent. So the problem is right at the top of the brushing function:
function brushed() {
main_x0.domain(brush.empty() ? mini_x0.domain() : brush.extent());
The domain set by brush.extent() is an array of two numbers, which then completely throws off your ordinal scale.
According to the wiki, if one of the scales attached to a brush function is an ordinal scale, the values returned by brush.extent() are values in the output range, not in the input domain. Ordinal scales don't have an invert() method to convert range values into domain values.
So, you have a few options on how to proceed:
You could re-do the whole graph using a linear time scale for your main x-axes instead of an ordinal scale. But then you have to write your own function to figure out the width of each day on that axis instead of being able to use .rangeBand().
You can create your own "invert" function to figure out which categorical values (dates on the mini_x0.domain) are included in the range returned by brush.extent(). Then you would have to both reset the main_x0.domain to only include those dates on the axis, and filter out your rectangles to only draw those rectangles.
Or you can leave the domain of main_x0. be, and change the range instead. By making the range of the graph larger, you space out the bars greater. In combination with a clipping path to cut off bars outside the plotting area, this has the effect of only showing a certain subset of bars, which is what you want anyway.
But what should the new range be? The range returned by brush.extent() is the beginning and end positions of the brushing rectangle. If you used these values as the range on the main graph, your entire graph would be squished down to just that width. That's the opposite of what you want. What you want is for the area of the graph that originally filled that width to be stretched to fill the entire plotting area.
So, if your original x range is from [0,100], and the brush covers the area [20,60], then you need a new range that satisfies these conditions:
the 20% mark of the new range width is at 0;
the 60% mark of the new range width is at 100.
Therefore,
the total width of the new range is ( (100-0) / (60-20) )*(100-0) = 250;
the start of the new range is at (0 - (20/100)*250) = -50;
the end of the new range is at (-50) + 250 = 200.
Now you could do all the algebra for figuring out this conversion yourself. But this is really just another type of scaling equation, so why not create a new scale function to convert between the old range and the zoomed-in range.
Specifically, we need a linear scale, with its output range set to be the actual range of the plotting area. Then set the domain according to the range of the brushed area that we want to stretch to cover the plotting area. Finally, we figure out the range of the ordinal scale by using the linear scale to figure out how far off the screen the original max and min values of the range would be. And from there, we-can resize the other ordinal scale and reposition all the rectangles.
In code:
//Initialization:
var main_xZoom = d3.scale.linear()
.range([0, main_width - 275])
.domain([0, main_width - 275]);
//Brushing function:
function brushed() {
var originalRange = main_xZoom.range();
main_xZoom.domain(brush.empty() ?
originalRange:
brush.extent() );
main_x0.rangeRoundBands( [
main_xZoom(originalRange[0]),
main_xZoom(originalRange[1])
], 0.2);
main_x1.rangeRoundBands([0, main_x0.rangeBand()], 0);
bar.selectAll("rect")
.attr("transform", function (d) {
return "translate(" + main_x0(d.date) + ",0)";
})
.attr("width", function (d) {
return main_x1.rangeBand();
})
.attr("x", function (d) {
return main_x1(d.portfolio);
});
main.select("g.x.axis").call(main_xAxis);
}
Working fiddle based on your simplified code (Note: you still need to set a clipping rectangle on the main plot):
http://fiddle.jshell.net/CjaD3/1/
I'm playing around with D3 and want tick lines to cut through a linear time graph across the vertical axis. The tick line elements are there, with the correct vectors, but they do not appear. What appears instead is the path element that runs horizontally with the tick labels.
JSFiddle Link
var width = 960;
var height = 200;
var container = d3.select(".timeTable");
var svg = container.append("svg")
.attr("width", width)
.attr("height", height);
var roomID = container.attr("data-room");
var times = [
{"from":"2012-12-27 00:00:00","until":"2012-12-27 12:00:00"},
{"from":"2012-12-27 00:00:00","until":"2012-12-27 23:59:00"},
{"from":"2012-12-27 02:00:00","until":"2012-12-27 04:00:00"},
{"from":"2012-12-27 03:00:00","until":"2012-12-27 21:00:00"},
{"from":"2012-12-27 03:30:00","until":"2012-12-27 04:50:00"},
{"from":"2012-12-27 05:00:00","until":"2012-12-27 12:00:00"},
{"from":"2012-12-27 09:00:00","until":"2012-12-27 15:00:00"},
{"from":"2012-12-27 13:00:00","until":"2012-12-27 23:00:00"},
{"from":"2012-12-27 13:00:00","until":"2012-12-27 23:30:00"},
{"from":"2012-12-27 20:00:00","until":"2012-12-27 23:59:00"},
{"from":"2012-12-27 20:00:00","until":"2012-12-27 22:00:00"},
{"from":"2012-12-27 23:00:00","until":"2012-12-27 23:30:00"},
{"from":"2012-12-28 01:00:00","until":"2012-12-28 13:00:00"}
];
function draw(times) {
// domain
var floor = d3.time.day.floor(d3.min(times, function (d) { return new Date(d.from); }));
var ceil = d3.time.day.ceil(d3.max(times, function (d) { return new Date(d.until); }));
// define linear time scale
var x = d3.time.scale()
.domain([floor, ceil])
.rangeRound([0, width]);
// define x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.ticks(d3.time.hours, 6)
.tickSize(4);
// draw time bars
svg.selectAll("rect")
.data(times)
.enter().append("rect")
.attr("class", "timeRange")
.attr("width", function (d, i) { return x(new Date(d.until)) - x(new Date(d.from)); })
.attr("height", "10px")
.attr("x", function (d, i) { return x(new Date(d.from)); })
.attr("y", function (d, i) { return i * 11; });
// draw x axis
svg.append("g")
.attr("class", "xAxis")
.attr("transform", "translate(0, " + (height - 23) + ")")
.call(xAxis);
}
draw(times);
The path element generated simply overlaps the ticks, but the ticks are not visible even with path removed.
The desired tick effect is shown here Population Pyramid - ticks on the vertical axis have a line that cuts through the rest of the graph.
Is there different behavior I need to be aware of for time scales?
Much appreciated.
Chrome 23, D3 v3
The trick to getting the tick lines into the plot area is to actually make a second axis and hide the labels. So your code plus the grid lines looks something like (fiddle):
// draw x axis
var xAxisLine = svg.append("g")
.attr("class", "xAxis")
.attr("transform", "translate(0, " + (height - 23) + ")")
.call(xAxis);
xAxisLine.selectAll("path.domain").attr("stroke","black").style('fill','none');
xAxisLine.selectAll("line.tick").style('stroke','black');
xAxis.tickSize(height-23);
var xAxisLineOver = svg.append("g")
.attr("class", "xAxis-overlay")
.attr("transform", "translate(0,0)")
.call(xAxis);
xAxisLineOver.selectAll("path.domain").attr("stroke-width",0).style('fill','none');
xAxisLineOver.selectAll("line.tick").style('stroke','red');
xAxisLineOver.selectAll("text").text("");
I'm not sure this is the exact same problem I had. What worked for me was:
.tick line{
stroke: black
}
Chrome is very strict regarding rendering SVG. So keep this in mind:
For axes specify their full RGB value (so #ff0000 instead of just #f00).
Path stroke widths are tricky. If they are less than 1 (px) and you have included
shape-rendering: crispEdges;
in CSS styles than any web browser might not display such path (or when the chart is resized to different size).
In that case comment or delete the "crispEdges" rendering and you should be fine (though in this case you leave the browser the smoothing decision).
As the title states, I have created a D3 line/area graph, and I am finding it difficult to get the graph's width to remain constant, depending on the amount of data I have given it to render, it scales the width of the graph accordingly, but I am unsure of how I can get it to remain at a constant width, regardless of the amount of data given, which is what I would like to achieve.
I imagine it has something to do with the scaling of the x and y coordinates, but I am stuck at the moment and can't seem to figure out why it is doing this.
Here is the code I have thus far,
//dimensions and margins
var width = 625,
height = 350,
margin = 5,
// get the svg and set it's width/height
svg = d3.select("#main")
.attr("width", width)
.attr("height", height);
//initialize the graph
init([
[12345,42345,32345,22345,72345,62345,32345,92345,52345,22345],
[1234,4234,3234,2234,7234,6234,3234,9234,5234,2234]
]);
$("button").live('click', function(){
var id = $(this).attr("id");
if(id == "one"){
updateGraph([
[52345,32345,12345,22345,62345,72345,92345,32345,22345,22345,52345,32345,12345,22345,62345,72345,92345,32345,22345,22345,52345,32345,12345,22345,62345,72345,92345,32345,22345,22345],
[4234,12345,2234,32345,6234,7234,9234,3234,2234,2234,4234,1234,2234,3234,6234,7234,9234,3234,2234,2234,4234,1234,2234,3234,6234,7234,9234,3234,2234,2234]
]);
}else if(id == "two"){
updateGraph([
[12345,42345,32345,22345,72345,62345,32345,92345,52345,22345,12345,42345,32345,22345,72345,62345,32345,92345,52345,22345,12345,42345,32345,22345,72345],
[1234,2345,3234,2234,7234,6234,3234,9234,5234,2234,1234,4234,3234,2234,7234,6234,3234,9234,5234,2234,1234,4234,3234,2234,7234]
]);
}
});
function init(data){
var x = d3.scale.linear()
.domain([0,data[0].length])
.range([margin, width-margin]),
y = d3.scale.linear()
.domain([0,d3.max(data[0])])
.range([height-margin, margin]),
/* line path generator */
line = d3.svg.line().interpolate('monotone')
.x(function(d,i) { return x(i); })
.y(function(d) { return y(d); }),
/* area path generator */
area = d3.svg.area().interpolate('monotone')
.x(line.x())
.y1(line.y())
.y0(y(0)),
groups = svg.selectAll("g")
.data(data)
.enter()
.append("g");
svg.select("g")
.selectAll("circle")
.data(data[0])
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", line.x())
.attr("cy", line.y())
.attr("r", 4);
/* add the areas */
groups.append("path")
.attr("class", "area")
.attr("d",area)
.style("fill", function(d,i) { return (i == 0 ? "steelblue" : "red" ); });
/* add the lines */
groups.append("path")
.attr("class", "line")
.attr("d", line);
}
function updateGraph(data){
var x = d3.scale.linear()
.domain([0,data[0].length])
.range([margin, width-margin]),
y = d3.scale.linear()
.domain([0,d3.max(data[0])])
.range([height-margin, margin]),
/* line path generator */
line = d3.svg.line().interpolate('monotone')
.x(function(d,i) { return x(i); })
.y(function(d) { return y(d); }),
/* area path generator */
area = d3.svg.area().interpolate('monotone')
.x(line.x())
.y1(line.y())
.y0(y(0));
groups = svg.selectAll("g")
.data(data),
circles = svg.select("g")
.selectAll("circle");
circles.data(data[0])
.exit().remove();
circles.data(data[0])
.enter().append("circle")
.attr("class", "dot")
.attr("cx", line.x())
.attr("cy", line.y())
.attr("r", 4);
/* animate circles */
circles.data(data[0])
.transition()
.duration(1000)
.attr("cx", line.x())
.attr("cy", line.y());
/* animate the lines */
groups.select('.line')
.transition()
.duration(1000)
.attr("d",line);
/* animate the areas */
groups.select('.area')
.transition()
.duration(1000)
.attr("d",area);
}
As well as a fiddle http://jsfiddle.net/JL33M/
Thank you!
The width of the graph depends on the range() you give it. range([0,100]) will always "stretch" the domain() values to take up 100 units.
That's what your code is currently doing:
var x = d3.scale.linear()
.domain([0,data[0].length])
.range([margin, width-margin]);// <-- always a fixed width
You want the width to depend on the number of data entries. Say you've decided you want each data point to take up 5 units, then range() needs to depend on the size of the dataset:
var x = d3.scale.linear()
.domain([0,data[0].length])
.range([margin, 5 * data[0].length]);// <-- 5 units per data point
Of course, under these conditions, your graph width grows with the dataset; if you give it a really long data array of, say, 500 points, the graph would be 2500 units wide and likely run off screen. But if your data is such that you know the maximum length of it, then you'll be fine.
On an unrelated note, I think your code could use a refactoring to be less repetitive. You should be able to achieve what you're doing with a single update() function, without the need for the init() function.
This tutorial by mbostock describe the "general update pattern" I'm referring to. Parts II and III then go on to explaining how to work transitions into this pattern.