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
Related
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 have tried to create a map of India with some points in it. I followed the codebase from here.
Everything is fine except the points. They are hidden behind other features on the map, and because of this are not visible. How do I layer the features so that the points are visible?
In d3.js map layering can be handled in two ways. If this is your code (paraphrasing from your example)
d3.json("path.json",function (json) {
g.selectAll("path")
.data(json.features)
.enter().append("path")
.attr("d", path);
});
d3.csv("path.csv",function (csv) {
g.selectAll("circle")
.data(csv)
.enter().append("circle")
.attr("cx", function(d) { projection([d.x,d.y])[0] })
.attr("cy", function(d) { projection([d.x,d.y])[1] })
.attr("r",4);
});
Data will be added to the 'g' element based on the order in which the callback functions are completed, so it is possible that the csv data will be drawn first and the json data will be drawn after it.
The first method I'll present here is the cleanest way in most situations to specify data layer order (in my mind). SVG 'g' elements are appended in the order that they are specified. This gives you easy control over the layering of data:
var gBackground = svg.append("g"); // appended first
var gDataPoints = svg.append("g"); // appended second
// ... and so forth
Then, all you have to do is specify to which 'g' element/layer data gets appended/inserted into. So, your code would look more like:
d3.json("path.json",function (json) {
gBackground.selectAll("path")
.data(json.features)
.enter().append("path")
.attr("d", path);
});
d3.csv("path.csv",function (csv) {
gDataPoints.selectAll("circle")
.data(csv)
.enter().append("circle")
.attr("cx", function(d) { projection([d.x,d.y])[0] })
.attr("cy", function(d) { projection([d.x,d.y])[1] })
.attr("r",4);
});
The second option appends data to the same 'g' element but ensures the order in which this is done is controlled, by drawing the second layer in the callback function that draws the first, after the first is drawn:
To control the ordering of the data with this method we would modify the code to something like:
d3.json("path.json",function (json) {
g.selectAll("path")
.data(json.features)
.enter().append("path")
.attr("d", path);
// once the json is drawn, draw the csv:
d3.csv("path.csv",function (csv) {
g.selectAll("circle")
.data(csv)
.enter().append("circle")
.attr("cx", function(d) { projection([d.x,d.y])[0] })
.attr("cy", function(d) { projection([d.x,d.y])[1] })
.attr("r",4);
});
});
Here is My fiddle
I have an initial array of points(objects) that I draw on my chart and then I want to create a voronoi overlay for mouse events.
Form of my point objects: (Fiddle lines: 4-12)
point {
id: 'id',
x: xCoordinate,
y: yCoordinate
}
And my voronoi code: (Fiddle lines: 95-112)
var voronoi = d3.geom.voronoi()
.x(function(d) {return x(d.x); })
.y(function(d) {return y(d.y); })
.clipExtent([[0,0],[w,h]]);
//Create the Voronoi grid
graph.selectAll("path")
.data(voronoi(points))
.enter().append("path")
.attr("d",function(d){return "M" + d.join("L") + "Z";})
.datum(function(d, i) { return d.point; })
.attr("class", function(d,i) { return "voronoi " + d.id; })
.style("stroke", "#000")
.style("fill", "#2074A0")
.style("opacity", ".3")
.style("pointer-events", "all")
.on("mouseover", function(d){document.getElementById('legend').innerHTML = d.id})
.on("mouseout", function(d){document.getElementById('legend').innerHTML = ''});
Problem is that var voronoi = d3.geom.voronoi()... returns numberOfPoints-4 polygons. The first 4 polygons are missing regardless of number of points. If number of points is 4 or less, no polygons are returned.
Is this a bug or is there an error in my code?
Found a problem. The catch was in selecting all paths with graph.selectAll("path") instead of just paths appended after data(voronoi(points))
I added a class attribute to appended paths, and then select all paths of that class only.
Here is the corrected fiddle.
I'm trying to adapt Mike Bostock's Wealth of Nations motion chart example to my own data set. Funny enough, I'm using GPS lata/long from train locomotives as the axes and datetimes instead of years.
Have a look at my jsfiddle (link).
This seems to be the key part to actually move the dots, but I'm not actually getting any motion.
// Add a dot per loco. Initialize the data at min(starttime), and set the colors.
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(minDateInt))
.enter().append("circle")
.attr("class", "dot")
.style("fill", function(d) { return colorScale(color(d)); })
.call(position)
.sort(order);
// Positions the dots based on data.
function position(dot) {
dot .attr("cx", function(d) { return xScale(x(d)); })
.attr("cy", function(d) { return yScale(y(d)); })
.attr("r", function(d) { return radiusScale(radius(d)); });
Does anyone know what's wrong with my code?
I'm working on a little project to print scaled down archery targets for practice at shorter range indoors. For example, the target face shown here:
...is 16cm in diameter at full size for 20 yards, but needs to be printed at half that size for practice at 10 yards. There are many different styles of target faces, and I'd like to support as many as possible. I have D3 working to pull the relevant sizes from a CSV file.
I'm specifying the sizes of the elements of the SVG in centimeters whenever possible. Is it possible to use D3 to generate a PDF that can be printed at a specific size?
Here's my code that loads the CSV and generates the SVG.
var dataset;
var w = "16cm";
var h = "16cm";
d3.csv("./data/nfaa5spot.csv", function(error, data) {
if (error) {
console.log(error);
} else {
console.log(data);
// Hand data off to global var for processing
dataset = data;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("viewBox", "-400,-400,1700,1700");
var circles = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr({
cx: function(d) { return d["cx"]; },
cy: function(d) { return d["cy"]; },
r: function(d) { return d["r"]; }
})
.style("fill", function(d) { return d["fill"]; })
.style("stroke", function(d) { return d["stroke"]; })
.style("stroke-width", function(d) { return d["stroke-width"]; });
}
});
Here are the contents of the relevant CSV file:
r,cx,cy,fill,stroke,stroke-width
16cm,8cm,8cm,#164687,#164687,0
12cm,8cm,8cm,#164687,white,3
8cm,8cm,8cm,white,white,0
4cm,8cm,8cm,white,#164687,3
Resizing is just first part. I'm also working on drawing additional lines on the target face to compensate for arrow shaft diameter at shorter range. Fun project, and a good intro to working with D3.