How can d3 areas have their transitions animated? I've seen examples for lines but can't find anything on animating an area.
Eg area:
var area = d3.svg.area()
.x(function(d) { return x(d.x); })
.y0(height)
.y1(function(d) { return y(d.y); });
Update: I've found an example for an area chart but I don't understand it. How is this function creating the area transition?
function transition() {
d3.selectAll("path")
.data(function() {
var d = layers1;
layers1 = layers0;
return layers0 = d;
})
.transition()
.duration(2500)
.attr("d", area);
}
The transition of areas works just like for other attributes. Only in case of areas, we are interpolating strings instead of interpolating numbers. When you call the area function with some data, then it produces a string which looks like M0,213L4,214L9,215 ... L130,255.7, which is a DSL used for the d attribute. When you change the data you pass to the area function, this string changes and D3 interpolates them.
Regarding the example you have found, the interesting bit which causes the transition is only this:
.transition()
.duration(2500)
.attr("d", area);
The other part merely is a fancy way of alternatively returning layers1 and layers0 as the data for the area function on consecutive calls.
d3.selectAll("path")
.data(function() {
var d = layers1;
layers1 = layers0;
return layers0 = d;
})
Thanks #neptunemo for your suggestion. However, your code is too specific for your problem. I would like to take a general case for better illustration of your idea:
Please see the full code from an example of d3noob: https://bl.ocks.org/d3noob/119a138ef9bd1d8f0a8d57ea72355252
Original code of area generator:
var area = d3.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.close); });
Modified code of area generator:
var area = function(datum, boolean) {
return d3.area()
.y0(height)
.y1(function (d) { return y(d.close); })
.x(function (d) { return boolean ? x(d.date) : 0; })
(datum);
}
datum is to take the data,
boolean is to control the:
.x() (in case you want the animation along x-axis)
.y1() (in case you want the animation along y-axis)
By setting boolean to false, we're able to set .x() or .y1() to 0.
This will help us to set the initial state of area before triggering the transition process.
Modified code of area transition:
svg.append("path")
.data([data])
.attr("class", "area")
.attr("d", d => area(d, false))
.attr("fill", "lightsteelblue")
.transition()
.duration(2000)
.attr("d", d => area(d,true));
Effects?
case of controlling .x()
case of controlling .y1()
Note: The issue I met is that I cannot synchronize the animation of line and area :(
Bit late to the party, but:
I solved the problem by modifying the original 'area' function, passing two variables: the data, and the field I wish to chart:
var area = function(datum, field) {
return d3.svg.area()
.x(function(d) {
return xScale(d.period_end);
})
.y0(height)
.y1(function(d) {
return yScale(d[field] || 0);
})(datum);
};
Then when you draw the path, just use basic transition. First time, passing no 'field', resulting in drawing zero values, and then - after transition() - passing the field I wanted:
areaChart.append('path')
.attr('class', 'area')
.attr('d', area(chartData))
.attr('fill', function() {
return chartColor;
})
.attr('opacity', 0.15)
.transition().duration(chartSettings.duration)
.attr('d', area(chartData, 'value'));
Works nicely without the need for sub functions. Exactly the same can of course be done for line charts.
Related
Very much D3 newbie here, feeling my way around adapting an existing radar chart built in D3 v3.5.9
I'm having an issue in interpolating between points when there are zero values between them.
Example:
radar when all data points are present, looks fine
radar when there are zero values
The behaviour I want is for the interpolation to go around the circle, rather than just closing the sections bounded by the non-zero values.
green lines show desired behaviour whenever a zero is encountered
I have used the 'defined' function to find zero values in the source data, but I need to add something else to instruct D3 to draw the connecting lines between the desired points. Something with the index value for d, probably?
Or perhaps 'defined' is not the right function in this case?
var radarLine = d3.svg.line
.radial()
.defined(function (d) {
return d.value !== 0;
})
.interpolate("linear-closed")
.radius(function (d) {
return rScale(d.value);
})
.angle(function (d, i) {
return i * angleSlice;
});
if (cfg.roundStrokes) {
radarLine.interpolate("cardinal-closed");
}
//Create a wrapper for the blobs
var blobWrapper = g
.selectAll(".radarWrapper")
.data(data)
.enter()
.append("g")
.attr("class", "radarWrapper");
//Append the backgrounds
blobWrapper
.append("path")
//.attr("class", "radarArea")
.attr("class", function (d) {
return "radarArea" + " " + d[0].radar_area.replace(/\s+/g, "");
})
.attr("d", function (d, i) {
return radarLine(d);
})
.style("fill", function (d, i) {
return cfg.color(i);
})
.style("fill-opacity", 0);
//Create the outlines
blobWrapper
.append("path")
.attr("class", "radarStroke")
.attr("d", function (d, i) {
return radarLine(d);
})
.style("stroke-width", cfg.strokeWidth + "px")
.style("stroke", function (d, i) {
return cfg.color(i);
})
.style("fill", "none");
Any help would be greatly appreciated!
I am trying to add red points at certain geolocations on the following map created by Mike Bostock. https://bl.ocks.org/mbostock/3795040. My points show up but don't move with the map. How do I edit the code to make the points move with the map. Thank you.
//add circles to svg
svg.selectAll("circle")
.data([wr,pt,sd,jp,fm])
.enter()
.append("circle")
.attr("cx", function (d) { console.log(projection(d)); return projection(d)[0]; })
.attr("cy", function (d) { return projection(d)[1]; })
.attr("r", "8px")
.attr("fill", "red");
The following is what the above array being referenced.
//points
var wr = [32.6130007, -83.624201];
var pt = [48.9334357, 8.961208];
var sd = [32.715738, -117.1610838];
var jp = [35.6894875, 139.6917064];
var fm = [39.1137602, -76.7267773];
You have to include the circles in the mousemove function:
svg.on("mousemove", function() {
var p = d3.mouse(this);
projection.rotate([λ(p[0]), φ(p[1])]);
svg.selectAll("path").attr("d", path);
//change the circles' positions here:
svg.selectAll("circle").attr("cx", function(d) {
console.log(projection(d));
return projection(d)[0];
})
.attr("cy", function(d) {
return projection(d)[1];
})
});
Here is the updated bl.ocks: https://bl.ocks.org/anonymous/2a6f07cdc12838b296674470ad715bbe/54d6de8d73347081f900c88a203019df74f23ade
PS: Some circles appear to move wrongly: they are correct, though. The thing is that they are behind the globe. To hide those circles you'll have to write another function (which is outside the scope of this answer).
An alternative to hide the circles behind the globe is using a path instead a circle. That way, the projection will automatically clip those paths. Have a look: https://bl.ocks.org/anonymous/9e640195e2c021cd79b5ca9b2238a44c/1c43719a7d6a85d0226cf3c468ac23e570add22d
I want to draw a pie chart for every point on the map instead of a circle.
The map and the points are displaying well but the pie chart is not showing over the map points. There is no error also. I can see the added pie chart code inside map also.
Below is the code snippet .
var w = 600;
var h = 600;
var bounds = [[78,30], [87, 8]]; // rough extents of India
var proj = d3.geo.mercator()
.scale(800)
.translate([w/2,h/2])
.rotate([(bounds[0][0] + bounds[1][0]) / -2,
(bounds[0][1] + bounds[1][1]) / -2]); // rotate the project to bring India into view.
var path = d3.geo.path().projection(proj);
var map = d3.select("#chart").append("svg:svg")
.attr("width", w)
.attr("height", h);
var india = map.append("svg:g")
.attr("id", "india");
var gDataPoints = map.append("g"); // appended second
d3.json("data/states.json", function(json) {
india.selectAll("path")
.data(json.features)
.enter().append("path")
.attr("d", path);
});
d3.csv("data/water.csv", function(csv) {
console.log(JSON.stringify(csv))
gDataPoints.selectAll("circle")
.data(csv)
.enter()
.append("circle")
.attr("id", function (d,i) {
return "chart"+i;
})
.attr("cx", function (d) {
return proj([d.lon, d.lat])[0];
})
.attr("cy", function (d) {
return proj([d.lon, d.lat])[1];
})
.attr("r", function (d) {
return 3;
})
.each(function (d,i) {
barchart("chart"+i);
})
.style("fill", "red")
//.style("opacity", 1);
});
function barchart(id){
var data=[15,30,35,20];
var radius=30;
var color=d3.scale.category10()
var svg1=d3.select("#"+id)
.append("svg").attr('width',100).attr('height',100);
var group=svg1.append('g').attr("transform","translate(" + radius + "," + radius + ")");
var arc=d3.svg.arc()
.innerRadius('0')
.outerRadius(radius);
var pie=d3.layout.pie()
.value(function(d){
return d;
});
var arcs=group.selectAll(".arc")
.data(pie(data))
.enter()
.append('g')
.attr('class','arc')
arcs.append('path')
.attr('d',arc)
.attr("fill",function(d,i){
return color(d.data);
//return colors[i]
});
}
water.csv:
lon,lat,quality,complaints
80.06,20.07,4,17
72.822,18.968,2,62
77.216,28.613,5,49
92.79,87.208,4,3
87.208,21.813,1,12
77.589,12.987,2,54
16.320,75.724,4,7
In testing your code I was unable to see the pie charts rendering, at all. But, I believe I still have a solution for you.
You do not need a separate pie chart function to call on each point. I'm sure that there are a diversity of opinions on this, but d3 questions on Stack Overflow often invoke extra functions that lengthen code while under-utilizing d3's strengths and built in functionality.
Why do I feel this way in this case? It is hard to preserve the link between data bound to svg objects and your pie chart function, which is why you have to pass the id of the point to your function. This will be compounded if you want to have pie chart data in your csv itself.
With d3's databinding and selections, you can do everything you need with much simpler code. It took me some time to get the hang of how to do this, but it does make life easier once you get the hang of it.
Note: I apologize, I ported the code you've posted to d3v4, but I've included a link to the d3v3 code below, as well as d3v4, though in the snippets the only apparent change may be from color(i) to color[i]
In this case, rather than calling a function to append pie charts to each circle element with selection.each(), we can append a g element instead and then append elements directly to each g with selections.
Also, to make life easier, if we initially append each g element with a transform, we can use relative measurements to place items in each g, rather than finding out the absolute svg coordinates we would need otherwise.
d3.csv("water.csv", function(error, water) {
// Append one g element for each row in the csv and bind data to it:
var points = gDataPoints.selectAll("g")
.data(water)
.enter()
.append("g")
.attr("transform",function(d) { return "translate("+projection([d.lon,d.lat])+")" })
.attr("id", function (d,i) { return "chart"+i; })
.append("g").attr("class","pies");
// Add a circle to it if needed
points.append("circle")
.attr("r", 3)
.style("fill", "red");
// Select each g element we created, and fill it with pie chart:
var pies = points.selectAll(".pies")
.data(pie([0,15,30,35,20]))
.enter()
.append('g')
.attr('class','arc');
pies.append("path")
.attr('d',arc)
.attr("fill",function(d,i){
return color[i];
});
});
Now, what if we wanted to show data from the csv for each pie chart, and perhaps add a label. This is now done quite easily. In the csv, if there was a column labelled data, with values separated by a dash, and a column named label, we could easily adjust our code to show this new data:
d3.csv("water.csv", function(error, water) {
var points = gDataPoints.selectAll("g")
.data(water)
.enter()
.append("g")
.attr("transform",function(d) { return "translate("+projection([d.lon,d.lat])+")" })
.attr("class","pies")
points.append("text")
.attr("y", -radius - 5)
.text(function(d) { return d.label })
.style('text-anchor','middle');
var pies = points.selectAll(".pies")
.data(function(d) { return pie(d.data.split(['-'])); })
.enter()
.append('g')
.attr('class','arc');
pies.append("path")
.attr('d',arc)
.attr("fill",function(d,i){
return color[i];
});
});
The data we want to display is already bound to the initial g that we created for each row in the csv. Now all we have to do is append the elements we want to display and choose what properties of the bound data we want to show.
The result in this case looks like:
I've posted examples in v3 and v4 to show a potential implementation that follows the above approach for the pie charts:
With one static data array for all pie charts as in the example: v4 and v3
And by pulling data from the csv to display: v4 and v3
I want to show the city name and population related to the voronoi area hovered over. However, with how I made the voronoi areas, I had to either only send coordinate data and have all of the drawings work, or send more data and none of the voronoi areas are drawn (b/c it can't read the non-coordinate data, and I don't know how to specify within an array or object, at least when creating voronois). I can enter static or irrelevant data for the tooltip (as I did below), but not anything from the actual dataset.
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text("a simple tooltip");
var voronoi = d3.geom.voronoi()
.clipExtent([[0, 0], [w, h]]);
d3.csv("us-cities1.csv", function(d) {
return [projection([+d.lon, +d.lat])[0], projection([+d.lon, +d.lat])[1]];
}, function(error, rows) {
vertices = rows;
drawV(vertices);
}
);
function polygon(d) {
return "M" + d.join("L") + "Z";
}
function drawV(d) {
svg.append("g")
.selectAll("path")
.data(voronoi(d), polygon)
.enter().append("path")
.attr("class", "test")
.attr("d", polygon)
.attr("id", function(d, i){return i;})
.on("mouseover", function(){return tooltip.style("visibility", "visible");})
.on("mousemove", function(){return tooltip.style("top", (event.pageY-10)+"px").style("left",(event.pageX+10)+"px").text((this).id);})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});
svg.selectAll("circle")
.data(d)
.enter().append("circle")
.attr("class", "city")
.attr("transform", function(d) { return "translate(" + d + ")"; })
.attr("r", 2);
}
I've put together an example using your data to demonstrate what Lars mentions. I created a variable for Voronoi like this:
var voronoi = d3.geom.voronoi()
.x(function(d) { return (d.coords[0]); })
.y(function(d) { return (d.coords[1]); });
which was taken from this Bl.ock by Mike. This allows you to specify the array of coordinates and still have them connected to the descriptive data you want to display.
I then created the object to store all the data in a format that could be used in the Voronio polygons using:
cities.forEach(function (d,i) {
var element = {
coords: projection([+d.lon, +d.lat]),
place: d.place,
rank: d.rank,
population: d.population
};
locCities.push(element);
});
I could have specified the translation of the coordinates in the voronio variable and then just used the cities variable, but I didn't.
The title attribute was used for the tooltips, but this can be replaced with something more appropriate such as what you have in your code. The relevant code is :
.append("title") // using titles instead of tooltips
.text(function (d,i) { return d.point.place + " ranked " + d.point.rank; });
There were a few issues with the data. I had to use an older version of d3 (3.1.5) to get the geojson to render correctly. I know there have been a number of chnanges to the AlberUsa projection so beware there is an issue there.
The location of some of the cities seems wrong to me for instance San Fancisco appears somewhere in Florida (this caused me some confusion). So I checked the original csv file and the coordinates seem to be wrong in there and the data is rendering where it should (just not where I'd expect according to the labels).
Now putting it all together you can find it here
I've started from the multiline example http://bl.ocks.org/mbostock/3884955
I extended it to show the dots along the line, but I'm not able to give to the circle the same color of the line...
I'm really new in d3.js and I really need an advice.
here the example page: http://www.danielepennati.com/d3/linea.html
I've change some variable name to make the script more general so there is some difference with the original example code. the main one is the name of the variable that contain the mapped data: it is "column" and not "cities"
d3.tsv(surce_data, function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "id"; }));
var column = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {id: d.id, value: +replace(d[name])};
})
};
});
the second main difference is the x axsis: in my code it is ordinal and not linear.
so to draw the line the code is:
var tracciato = svg.selectAll(".line-group")
.data(column)
.enter().append("g")
.attr("class", "line-group");
tracciato.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
to make the points along the line I've wrote this code:
var point = tracciato.append("g")
.attr("class", "line-point");
point.selectAll('circle')
.data(function(d,i){ return d.values})
.enter().append('circle')
.attr("cx", function(d, i) {
return x(i) + x.rangeBand() / 2;
})
.attr("cy", function(d, i) { return y(d.value) })
.attr("r", 5);
I'd link the points color be the same of the line, but the problem is the colors are assigned to the "column" object. I don't know how to give to each new circle within the same column the same column color...
I don't know if my problem is clear, please ask me if you need any more specification.
Thanks