D3js draws only one feature within geoJSON FeatureCollection - javascript

although being pretty new to D3js I already get most of the things up and running. However, what is not yet working, is the display of a geoJSON-based map of inner-city boundaries (data: http://ddj.haim.it/data/muenchen.geojson).
Here's what I have been trying lately:
var nWidth = 800, nHeight = 600,
oMap = d3.select('body').append('svg').attr('width', nWidth).attr('height', nHeight),
oProjection = d3.geo.mercator().center([ 11.591, 48.139 ]).scale(50000).translate([nWidth / 2, nHeight / 2]),
oPath = d3.geo.path().projection(oProjection);
d3.json('data/muenchen.geojson', function(_mError, _oCollection) {
oMap.append('g')
.selectAll('path')
.data(_oCollection.features)
.enter()
.append('path')
.attr('class', 'entity')
.attr('d', oPath);
});
What do I get? A map where only the last featue is drawn correctly and another one is drawn as one huge rectangle (well, in the source, it's not a rect, it's a path).
Any help greatly appreciated.
Thanks a bunch,
Mario

Not sure whether I get your point, Lars. I can see the last polygon from the geoJSON, however, only the last one. Here's the current output: http://ddj.haim.it/d3.html
EDIT: Okay, I got your point. Sorry and thanks a lot for the answer.
The key thing is to "fill" the paths (in the CSS) with transparency.

Related

D3 projection of GPS coordinates showing interesting behaviour [duplicate]

I am trying to visualize russians regions. I got data from here, validate here and all was well - picture.
But when I try to draw it, I receive only one big black rectangle.
var width = 700, height = 400;
var svg = d3.select(".graph").append("svg")
.attr("viewBox", "0 0 " + (width) + " " + (height))
.style("max-width", "700px")
.style("margin", "10px auto");
d3.json("83.json", function (error, mapData) {
var features = mapData.features;
var path = d3.geoPath().projection(d3.geoMercator());
svg.append("g")
.attr("class", "region")
.selectAll("path")
.data(features)
.enter()
.append("path")
.attr("d", path)
});
Example - http://ustnv.ru/d3/index.html
Geojson file - http://ustnv.ru/d3/83.json
The issue is the winding order of the coordinates (see this block). Most tools/utilities/libraries/validators don't really care about winding order because they treat geoJSON as containing Cartesian coordinates. Not so with D3 - D3 uses ellipsoidal math - benefits of this is include being able to cross the antimeridian easily and being able to select an inverted polygon.
The consequence of using ellipsoidal coordinates is the wrong winding order will create a feature of everything on the planet that is not your target (inverted polygon). Your polygons actually contain a combination of both winding orders. You can see this by inspecting the svg paths:
Here one path appears to be accurately drawn, while another path on top of it covers the entire planet - except for the portion it is supposed to (the space it is supposed to occupy covered by other paths that cover the whole world).
This can be simple to fix - you just need to reorder the coordinates - but as you have features that contain both windings in the same collection, it'll be easier to use a library such as turf.js to create a new array of properly wound features:
var fixed = features.map(function(feature) {
return turf.rewind(feature,{reverse:true});
})
Note the reverse winding order - through an odd quirk, D3, which is probably the most widespread platform where winding order matters actually doesn't follow the geoJSON spec (RFC 7946) on winding order, it uses the opposite winding order, see this comment by Mike Bostock:
I’m disappointed that RFC 7946 standardizes the opposite winding order
to D3, Shapefiles and PostGIS. And I don’t see an easy way for D3 to
change its behavior, since it would break all existing (spherical)
GeoJSON used by D3. (source)
By rewinding each polygon we get a slightly more useful map:
An improvement, but the features are a bit small with these projection settings.
By adding a fitSize method to scale and translate we get a much better looking map (see block here):
Here's a quick fix to your problem, projection needs a little tuning, also path has fill:#000 by default and stroke: #FFF could make it more legible.
var width = 700, height = 400;
var svg = d3.select(".graph").append("svg")
.attr("viewBox", "0 0 " + (width) + " " + (height))
.style("max-width", "700px")
.style("margin", "10px auto");
d3.json("mercator_files/83.json", function (error, mapData) {
var features = mapData.features;
var center = d3.geoCentroid(mapData);
//arbitrary
var scale = 7000;
var offset = [width/2, height/2];
var projection = d3.geoMercator().scale(scale).center(center)
.translate(offset);
var path = d3.geoPath().projection(projection);
svg.append("g")
.attr("class", "region")
.selectAll("path")
.data(features)
.enter()
.append("path")
.attr("d", path)
});

D3 Map Projection Not Showing Map

Hi i am trying to create a map of the city of Birmingham, though i can see the paths have been generated and the data is being loaded. I do not see anything on the html page.
I have seen people use the projection function without setting center or translate and it would visual their maps but this has not worked for me.
I have looked into possible solutions and found centering your projection by the city you are interested in should help with getting the map to display properly but this did not helped. I also tried to play around with the scale but this also did not help.
Essentially my expected results was a map of Birmingham to be displayed in the middle of my svg object.
var w= 1400;
var h = 700;
var svg = d3.select("body").append("svg").attr("width",w).attr("height",h );
var projection = d3.geoMercator().translate([w/2, h/2]).scale(100).center([1.8904,52.4862]);
var path = d3.geoPath().projection(projection);
var ukmap = d3.json("https://martinjc.github.io/UK-GeoJSON/json/eng/wpc_by_lad/topo_E08000025.json");
// draw map
Promise.all([ukmap]).then(function(values){
var map = topojson.feature(values[0],values[0].objects.E08000025).features
console.log(map);
svg.selectAll("path")
.data(map)
.enter()
.append("path")
.attr("class","continent")
.attr("d", path)
.style("fill", "#f0e4dd") //steelblue
});
```
It looks like the path is in the middle of your svg:
It's just really small.
With a d3 Mercator projection scale of 100 you are displaying 360 degrees of longitude across 100 pixels. So, with an svg that is 1400 pixels across, you could be showing 14 earths. Not ideal for a city. If you up the scale value to 10000 you'll at least see your feature, but it's not quite centered and it's still pretty small, try values for center and scale like so:
.center([-1.9025,52.4862])
.scale(100000)
(keeping the translate the same)
Now we're getting somewhere:
But this is still tedium, we can simply use projection.fitExtent or projection.fitSize to do the scaling automagically:
Promise.all([ukmap]).then(function(values){
var map = topojson.feature(values[0],values[0].objects.E08000025)
projection.fitSize([w,h],map);
var features = map.features;
svg.selectAll("path")
.data(features)
.enter()
...
This stretches the feature to fill the specified dimensions (it takes a geojson object, not an array, hence my slight restructuring). We can also specify a margin like so:
projection.fitExtent([[100,100],[w-100,h-100]],map);
This provides a 100 pixel margin around the map so it doesn't touch the edge of the SVG.
Both of these methods, fitSize and fitExtent, automatically set the projeciton translate and scale, so we can actually skip setting the scale, center, and translate manually (translate and scale essentially do the same thing: one after projection and one before, respectively. It's usually easier to use both though when setting the projection parameters manually)

D3: gauge chart with growing arc

I want to achieve something like a growing arc which indicates 5 levels (see picture). My data has only an integer value which is between 1-5. You can ignore the icon in the middle for now. Is there any possibility to achieve something like that in d3? I couldn't find any example for this. Moreover I tried it with a cut off pie (donut) chart approach, but I couldn't make the growing arc... I would appreciate any help! Thanks for that.
You can do this with d3 without dependency on external images, SVG sprites or anything in the DOM — just d3.js.
Here's a working fiddle. The implementation is explained below.
But also, here's a more advanced fiddle that animates a clip-path over the growing arc. Check out its predecessor to see how the mask looks without clipping.
First, you need to represent the graphics as an array of data that you bind to with d3. Specifically, you need a color and a "line command" (the string you assign to d as in <path d="...">. Something like this:
var segmentData = [
{ color:"#ED6000", cmd:"M42.6,115.3c5.2,1.5,11,2.4,16.8,2.4c1.1,0,2.7,0,3.7-0.1v-2.2c-7,0-13.1-1.3-18.8-3.6L42.6,115.3z" },
{ color:"#EF7D00", cmd:"M25.7,99.3c4.3,4.7,9.5,8.6,15.3,11.3l-1.4,3.8c-6.9-2.4-13.2-6.1-18.6-10.8L25.7,99.3z" },
{ color:"#F4A300", cmd:"M23.7,97c-5.2-6.4-8.8-14-10.3-22.4L2.9,75.7c2.9,10,8.5,18.9,15.8,25.9L23.7,97z" },
{ color:"#F7BC00", cmd:"M13,71.5c-0.2-2-0.4-4-0.4-6c0-10.7,3.4-20.6,9.2-28.8L9.4,28.3c-5.6,9-8.9,19.6-8.9,30.9 c0,4.6,0.6,9.1,1.6,13.5L13,71.5z" },
{ color:"#FFCF36", cmd:"M63,15.7V0.8c-1-0.1-2.5-0.1-3.7-0.1c-19.9,0-37.5,9.9-48.1,25l12.7,8.6C33.1,23,46,15.7,63,15.7z" }
];
Then you need an empty <svg> and probably a <g> within it, into which to draw the graphics:
var svg = d3.select("body").append("svg")
.attr("width", 125)
.attr("height", 125);
var gauge = svg.append("g");
Then you use d3 binding to create the segments:
var segments = gauge.selectAll(".segment")
.data(segmentData);
segments.enter()
.append("path")
.attr("fill", function(d) { return d.color; })
.attr("d", function(d) { return d.cmd; });
This just creates the graphic, but doesn't color it based on an integer value. For that, you can define an update function:
function update(value) {
segments
.transition()
.attr("fill", function(d, i) {
return i < value ? d.color : "#ccc";
})
}
Calling update(4) will color all but the last segment. Calling update(0) color none (leaving all of them gray).
In the fiddle, there's also a tick() function that calls update with a new value on a setTimeout basis, but that's just for demo.
Finally, if you wish, you can wrap all that code up and create a reusable component by following the advice in [this article].(http://bost.ocks.org/mike/chart/)
since it is relatively simple picture, I'd use a sprite, with 5 variations.
That would be much easier than using d3 and gives the same result.
(you could use some online tool like http://spritepad.wearekiss.com/ )
If you want to mimic duolingo progress images you can just simply copy their solution with own images. They are using sprites as this one: http://d7mj4aqfscim2.cloudfront.net/images/skill-strength-sprite2.svg not the d3.js approach. This will save you a lot of time and effort.

How to make multiple charts with d3.chart using nested data

I'm trying to create several charts using the d3.chart framework. This seems like it would be a common use case: the whole point of d3.chart is for the charts to be reusable after all! If you can just point me to an example that would be awesome (:
I went through this (https://github.com/misoproject/d3.chart/wiki/quickstart) tutorial to create a very basic "circle graph". (I copied the code exactly). Now what I want to do is create a separate chart for several sets of data.
I edited it slightly.
Before editing, to set up the chart we called:
var data = [1,3,4,6,10];
var chart = d3.select("body")
.append("svg")
.chart("Circles")
.width(100)
.height(50)
.radius(5);
chart.draw(data);
I tried to change it to:
var data = [{key:1, values:[1,3,4,6,10]},
{key:2, values:[5,2,10,8,11]},
{key:3, values:[1,5,9,16,12]}]
var chart = d3.select("body")
.selectAll("chart)
.data(data)
.enter()
.append("svg")
.chart("Circles")
.width(100)
.height(50)
.radius(5);
chart.draw(function(d) { return d.values; });
This doesn't work however. You can see the corner of a circle in 3 diferent places, but
the rest of it is cut off. However if replace
chart.draw(function(d) { return d.values; });
with
chart.draw([1,3,4,6,10]);
it correctly generates 3 circle graphs, all with that one dataset. And when I add
chart.draw(function(d) { console.log(d.values) return d.values; });
The console shows the 3 arrays I'm trying to pass it! I don't understand what is happening here that's breaking the code. Shouldn't it be the exact same thing as passing the actual arrays to 3 separate charts?
Here's a link to the JS bin with it set up: http://jsbin.com/jenofovogoke/1/edit?html,js,console,output Feel free to mess around with it!
The code is wayyy at the bottom.
I'm pretty new to java script and d3, and entirely new to d3.chart. Any help would be super appreciated!
I asked Irene Ros, who helps run d3.chart, and she informed me that the problem is that d3.chart's draw method can only take an array or a data object- it cannot take a function. She gave me a few helpful hints for ways to get around this: by using a transform function to edit my data within the chart, rather than using a function, or by creating a chart that holds multiple charts (see https://gist.github.com/jugglinmike/6004102 for a great example of this).
However in the end I found the simplest solution for me was to manually set the data. It feels like a bit off a hack because D3 does this for you already, but it was much simpler than changing the whole set up of my chart, and allows for nested data (yay!).
var svg = d3.select("body")
.selectAll("svg")
.data(data)
.enter()
.append("svg");
svg.each(function(d, i) {
var chart = d3.select(this)
.chart("Circles")
.height(50)
.width(100)
.radius(5)
var data = d.values;
chart.draw(data);
});

d3.js and geo projections albers/centers

Below is a bit of code that I've written and I'm hoping someone can help me out and explain why it's not responding the way I imagined it would.
I have (quite obviously) pieced this together from a number of examples, docs, etc. online and am using d3.v3.js. I want to better understand exactly what the center, scale, and translate 'attributes' of a projection do, so in addition to the large quantity of reading I've done, I thought I'd write a brief script that allows the user to click a new 'center' for the map - so in effect, you should be able to click this map (made with some data available in the gallery) and recenter the map on a new state/location/etc.
The issue is that every time I set a new center for the data as the inversely projected point that was clicked (that is, invert the point to get the new coordinates to set the center to), the map centers on alaska, then I can click that general area a few times and the world 'rotates' back into view.
What am I doing wrong? I thought this script would help me gain a better understanding of what's going on, and I'm getting there, but I would like a little help from you if at all possible.
<script>
var w = 1280;
var h = 800;
var proj = d3.geo.albers();
var path = d3.geo.path()
.projection(proj);
var svg = d3.select("body")
.insert("svg:svg")
.attr("height", h)
.attr("width", w);
var states = svg.append("svg:g")
.attr("id", "states");
d3.json("./us-states.json", function(d) {
states.selectAll("path")
.data(d.features).enter()
.append("svg:path")
.attr("d", path)
.attr("class", "state");
});
svg.on("click", function() {
var p = d3.mouse(this);
console.log(p+" "+proj.invert(p));
proj.center(proj.invert(p));
svg.selectAll("path").attr("d", path);
});
</script>
You should be able to use projection.rotate()
svg.on("click", function() {
p = proj.invert(d3.mouse(this));
console.log(p);
proj.rotate([-(p[0]), -(p[1])]);
svg.selectAll("path").attr("d", path);
});

Categories

Resources