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);
});
});
Related
I try to add labels to map done with d3-geomap, but can't make it work.
The choropleth map itself gets painted correctly, but adding the labels doesn't work out right. The labels show up on the wrong position.
After painting the map I loaded again the topojson file again and then add text blocks like that:
d3.json("https://d3-geomap.github.io/d3-geomap/topojson/countries/ESP.json").then(function(es) {
let path = d3.geoPath().projection(d3.geoMercator());
svg.select("svg").append("g")
.selectAll("labels")
.data(topojson.feature(es, es.objects.units).features)
.enter().append("text")
.attr("class", "place-label")
.attr("x", function(d) { return path.centroid(d)[0]; })
.attr("y", function(d) { return path.centroid(d)[1]; })
.attr("text-anchor","middle")
.text(function(d) { return d.properties.name; });
});
The problem here is that I can't figure out the correct position of the labels. I also tried to apply the same transform as to the polygons, but then have all the same y position.
Here is the example on bl.ocks.
I made some changes to your code and published it in this gist. When testing it locally, the map displayed like the image below. At this size, labels don't work well, but if you resize the map and/or show fewer labels it should be okay.
Some info on the changes. Whenever you want to draw something on top of a map with d3-geomap, it should go in the postUpdate function. This way the map is already rendered and its SVG elements, the geo data and the path object are accessible via the map object you created. No need to load the Topojson file a second time. The function passed to postUpdate looks like follows:
function drawLabels() {
map.svg.append("g").attr('class', 'zoom')
.selectAll("text")
.data(topojson.feature(map.geo, map.geo.objects.units).features)
.enter().append("text")
.attr("class", "place-label")
.attr("x", function(d) { return map.path.centroid(d)[0]; })
.attr("y", function(d) { return map.path.centroid(d)[1]; })
.attr("text-anchor","middle")
.text(function(d) { return d.properties.name; })
.on('click', map.clicked.bind(map));
}
This page of the documentation shows the available map attributes and accessor functions. Hope this helps.
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 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
So I'm using code based off of this..
http://bl.ocks.org/mbostock/3884955
Essentially, what I'm trying to do is at every data point I want to add a circle. Any help would be much appreciated seeing that I have no idea where to start.
This is my code so far: It worked when I was using single lines.
var circlegroup = focus.append("g")
circlegroup.attr("clip-path", "url(#clip)")
circlegroup.selectAll('.dot')
.data(data)
.enter().append("circle")
.attr('class', 'dot')
.attr("cx",function(d){ return x(d.date);})
.attr("cy", function(d){ return y(d.price);})
.attr("r", function(d){ return 4;})
.on('mouseover', function(d){ d3.select(this).attr('r', 8)})
.on('mouseout', function(d){ d3.select(this).attr('r', 4)});
You need nested selections for this. Assuming that data is a two-dimensional array, you would do something like this.
var groups = svg.selectAll("g").data(data).enter().append("g");
groups.data(function(d) { return d; })
.enter()
.append("circle")
// set attributes
My goal is to remove a data point from my bar chart.
It will then update itself:
Update X and Y axis
Update the actual bar chart
Update the legend
Issues I am having:
When I exit().remove() the rectangles in my graph, the code also gets rid of the rectangles in the legend. When I try to enter() the rectangles in my legend, they do not appear. I am not sure what is happening here, but I am not being successful in adding and removing elements due to data changes. Any help would be appreciated.
Code snippet of where I think I am having issues:
This is the part that also deletes the rectangles in the legend (I am not sure if I should do this here or in the enter/update/delete part of the legend)
The code below is executed right after the user clicks the "delete all" button. My intent here is to only delete one bar (the one named "ALL") and update the chart.
//Select rectangles
var bars = svg.selectAll("rect")
.data(dataset, function(d) { return d.State; });
//Enter rectangles
bars.enter()
.append("rect")
.style("fill", function(d,i) { return color(i) })
.attr("x", function(d) { return xScale(d.State); })
.attr("y", function(d) { return yScale(d.CustomerCount) })
.attr("width", xScale.rangeBand()) //returns rangeRoundBands width
.attr("height", function(d) { return h - yScale(d.CustomerCount) });
//Update rectangles
bars.transition()
.duration(1000)
.style("fill", function(d,i) { return color(i) })
.attr("x", function(d) { return xScale(d.State); })
.attr("y", function(d) { return yScale(d.CustomerCount) })
.attr("width", xScale.rangeBand()) //returns rangeRoundBands width
.attr("height", function(d) { return h - yScale(d.CustomerCount) });
//Exit rectangles
bars.exit()
.transition()
.duration(500)
.attr("x", w)
.remove();
Here is entire the code:
http://plnkr.co/edit/Nue5bocQsI4E6D5wfNSP
Here is the slightly smaller code:
I tried to reduce the code as much as possible but it is still pretty big.
http://jsfiddle.net/aNQWV/
In the part where you update the data you say:
var bars = svg.selectAll("rect")
.data(dataset, function(d) { return d.State; });
The problem is that at this point the svg contains two kind of rects. One kind corresponds to the bars, and the other ones are part of the legend. This means that you are joining the new data to this mix of rects, while actually you wanted to join the new data with the bars only.
So, you need a more specific selector that targets only the bar rects. The typical approach is to add a class to those rects when you create them:
svg.selectAll(".bar")
.data(input)
.enter().append("rect")
.attr("class", "bar");
Then in the part where you update the data you would say:
var bars = svg.selectAll(".bar")
.data(dataset, function(d) { return d.State; });