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’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.
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.
Okay, I'm starting to get a little more familiar with D3 but am still a little hazy on some things. I'm now trying to draw grid lines but am realizing that I may be hacking away versus doing it correctly. I tried to add some gridlines, using a tutorial, but ended up with a lot of code that I seem to be jimmy rigging in order to get it to line up properly. I was wondering if anyone could point me to a better way of writing this...
The original code is this.
<script type="text/javascript">
//Width and height
var w = 800;
var h = 400;
var padding = 20;
var border=1;
var bordercolor='black';
var dataset = [
[5, 20], [480, 90], [250, 50], [100, 33], [330, 95],[-50,-100],[50,-45],
[410, 12], [475, 44], [25, 67], [85, 21], [220, 88],[-480, -467], [3,-90],[468,481]
];
// create scale functions
var xScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[1]; })])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain( [-100, d3.max(dataset, function(d) { return d[1]; })] )
.range([2,5]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("border",border)
;
//define X axis this is rly a function, remember, variables can hold functions in JS
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(1)
.tickSize(-h, 0, 0)
; //Set rough # of ticks
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(1)
.tickSize(-w, 0, 0)
;
//create the circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 3);
// draw axes here
svg.append("g")
.attr("class", "axis") //assign "axis" class
.attr("transform", "translate(0," + (h - padding) +")")
.call(xAxis);
svg.append("g")
.attr("class", "axis") //assign "axis" class
.attr("transform", "translate(" + padding + ",0)" )
.call(yAxis);
// end draw axes here
</script>
and the code I added in the second link is here
var vis = svg.append("svg:g")
.attr("transform", "translate(20,0)")
var rules = vis.append("svg:g").classed("rules", true)
rules.append("svg:g").classed("grid x_grid", true)
.attr("transform", "translate(-20,"+h+")")
.call(d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(4)
.tickSize(-h,0,0)
.tickFormat("")
)
rules.append("svg:g").classed("grid y_grid", true)
.call(d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5)
.tickSize(-w,0,0)
.tickFormat("")
)
rules.append("svg:g").classed("labels x_labels", true)
.attr("transform", "translate(-20,"+ h +")")
.call(d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(4)
.tickSize(0)
.tickFormat("")
// .tickFormat(d3.time.format("%Y/%m"))
)
rules.append("svg:g").classed("labels y_labels", true)
.call(d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5)
.tickSubdivide(1)
.tickSize(0, 0, 0)
.tickFormat("")
)
Again, really appreciate any help
Assuming that you have Mike Bostock's standard margins defined and you have defined a linear scale for the y-axis the following code will create horizontal gridlines without using tickSize().
svg.selectAll("line.horizontalGrid").data(yScale.ticks(4)).enter()
.append("line")
.attr(
{
"class":"horizontalGrid",
"x1" : margin.right,
"x2" : width,
"y1" : function(d){ return yScale(d);},
"y2" : function(d){ return yScale(d);},
"fill" : "none",
"shape-rendering" : "crispEdges",
"stroke" : "black",
"stroke-width" : "1px"
});
I would suggest to use d3.svg.axis().scale() to tie up the grid to your coordinates. I drew a quick example based on your code: http://jsfiddle.net/temirov/Rt65L/1/
The gist is to use the existing scales, x and y, and to use ticks as grid. Since yAxis and xAxis are already defined we can just re-use them. Here is the relevant code:
//Draw a grid
var numberOfTicks = 6;
var yAxisGrid = yAxis.ticks(numberOfTicks)
.tickSize(w, 0)
.tickFormat("")
.orient("right");
var xAxisGrid = xAxis.ticks(numberOfTicks)
.tickSize(-h, 0)
.tickFormat("")
.orient("top");
svg.append("g")
.classed('y', true)
.classed('grid', true)
.call(yAxisGrid);
svg.append("g")
.classed('x', true)
.classed('grid', true)
.call(xAxisGrid);
You could use the ticks() function of your scale to get the tick values and then use them in a data call to draw the lines.
var ticks = xScale.ticks(4);
rules.selectAll("path.xgrid").data(ticks).enter()
.append("path")
.attr("d", function(d) {
return "M" + xScale(d) + " " + padding + "L" + xScale(d) + " " + (h-padding);
});
You may prefer using a line generator for the grid lines instead of creating the path manually. This works similarly for y grid lines, only that the y coordinate is constant and ranges from 0 to width of graph. You may need to adjust the start and end values to make it look "nice".
In the d3fc project we have created a gridlines component that renders in exactly the same way as the D3(v4) axis.
Here's an example of the usage:
var width = 500, height = 250;
var container = d3.select("#gridlines")
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scaleLinear()
.range([0, 100]);
var yScale = d3.scaleLinear()
.range([0, 100]);
var gridline = fc.annotationSvgGridline()
.xScale(xScale)
.yScale(yScale);
container.append("g")
.call(gridline);
Which renders as follows:
The spacing of the gridlines is determined by the ticks supplied by the associated axes.
Disclosure: I am a core contributor to the d3fc project!
Following #arete's idea, you can use the following to avoid re-drawing unnecessarily the gridline:
function createsGrid(data) {
var grid = gridLine.selectAll("line.horizontalGrid").data(scaleY.ticks());
grid.enter()
.append("line")
.attr("class","horizontalGrid");
grid.exit().remove();
grid.attr({
"x1":0,
"x2": width,
"y1": function (d) { return scaleY(d); },
"y2": function (d) { return scaleY(d); }
});
}
and define the following in your CSS file
line.horizonalGrid{
fill : none;
shape-rendering : crispEdges;
stroke : black;
stroke-width : 1.5px;
}
You could just use innerTickSize, instead of tickSize:
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(1)
.innerTickSize(-h);
Use tickSizeInner()
// x axis
var x = d3.scaleLinear().range([0, width]).domain([0, 100000]);
svg
.append("g")
.call(d3.axisBottom(x).ticks(10).tickSizeInner(-height))
So I'm new to D3 and have little exp with JavaScript in general. So I have been following some tutorials am currently using source code that creates a basic scatter plot. Now my question is how do I use the transition() method to moves the circles around when I add more datasets? I want to be able to set up buttons and when a user presses them, it activates the transition() method with the corresponding dataset. The tutorial I read on transitions only showed a transition on a single rectangle and did it manually, without data, and not with multiple items
//Width and height
var w = 900;
var h = 600;
var padding = 30;
//Static dataset
var dataset = [
[50, 30], [300, 75], [123, 98], [70, 40], [247, 556],
[410, 12], [475, 44], [25, 67], [85, 21], [220, 88],
[600, 150]
];
//Create scale functions
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[1]; })])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[1]; })])
.range([4, 4]);
//Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", function(d) {
return rScale(d[1]);
})
.attr("fill", "blue");
//Create X axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
First, before addressing the transition issue, you need to refactor things a bit. You're going to want to call an update(newData) function every time your data changes, and have this function do all the necessary updates.
This tutorial by mbostock describes exactly the "general update pattern" you'll need.
Parts II and III then go on to explaining how to work transitions into this pattern.
They're very short. And once you understand them, you'll have just about all the info you need to do this.
I guess you just have to specify .transition() function after .data(newData) function
In the following example Y2 is a node in a JSON file, where Y1 was the previous one used
Example:
//Creating the button
var button = d3.select("body")
.append("input")
.attr("type","button")
.attr("value", "A button");
//Transitioning process
button.on("click", function()
{ circles
.data(data.Y2)
.transition()
.attr("cx", function(d)
{
return d[0];
}
)
.attr("cy", 300);
}
)