I am trying to draw a map using topoJson so I followed this example
http://bl.ocks.org/mbostock/4122298
but I am not getting anything drawn.
here's what i wrote
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<style>
path {
fill: #ccc;
stroke: #fff;
stroke-width: .5px;
}
path:hover {
fill: red;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 960,
height = 500;
var path = d3.geo.path();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("tunisia.json", function(error, topology) {
console.log(topology);
svg.selectAll("path")
.data(topojson.feature(topology, topology.objects.governorates).features)
.enter().append("path")
.attr("d", path);
});
</script>
</body>
</html>
After some debugging it turns out that the path in my case is added as follows:
<svg width="960" height="500">
<path></path>
<path></path>
</svg>
whereas it should normally be like this:
<svg width="960" height="500">
<path d="M183.85631949544694,17.16574961388676L184.64695256075555,18.261986556132797L184.24437929962187,21.436416964644536L184.9109502450185,22.72190753660925L183.42733139583214,23.600229178621248L181.43637647772152,23.38526266060535L162.4858998398068,18.04698631290296L162.95134674943927,16.322885588815097L161.24381018256219,15.20848145955324L160.04585728433227,11.701769628478132L161.0879861841512,10.793553936506555L172.9773901748378,14.256236175137701Z"></path>
</svg>
here is the data I am using:
https://raw.githubusercontent.com/mtimet/tnacmaps/master/topojson/tunisia.json
could you please check what I am doing wrong
There is no problem with your json file.
The issue you are having is that you are not defining a projection for your d3.geo.path() which means it falls back to the default. According to the documentation linked above:
#d3.geo.path()
Creates a new geographic path generator with the default settings: the albersUsa projection and a point radius of 4.5 pixels.
Your geo data is for a map of Tunisia, so an albersUsa projection won't contain any of the coordinates in your dataset. That is why the path data is empty in your output.
To fix this, you need to define a projection. You can do this when you load your data, and you can use d3.geo.bounds(), passing in your featureCollection to find the geographic boundaries of your data.
var featureCollection = topojson.feature(topology, topology.objects.governorates);
var bounds = d3.geo.bounds(featureCollection);
Then from these boundaries, you can calculate the center of your featureCollection:
var centerX = d3.sum(bounds, function(d) {return d[0];}) / 2,
centerY = d3.sum(bounds, function(d) {return d[1];}) / 2;
Then you can use this to center your projection. For example, if you're using a mercator projection, you could do the following:
var projection = d3.geo.mercator()
.scale(3000)
.center([centerX, centerY]);
The choice of a scale of 3000 is arbitrary, it just seemed to work well in this case, tweak it to whatever works for you.
Finally, you need to set the .projection() of your path to the projection you made, before actually creating the svg paths.
path.projection(projection);
svg.selectAll("path")
.data(featureCollection.features)
.enter().append("path")
.attr("d", path);
HERE is a working example using your data.
Related
I am trying to draw SVG circles on a map background, but while they are showing up in the elements (using Chrome Dev tools) they are not shown on the page. What am I missing here, why are they hidden?
I have tried to change the fill, opacity of the map and of the circle but I can't figure out why it isn't rendering?
My code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Leaflet and D3 map</title>
<link rel="stylesheet" href="../leaflet.css" />
<script type="text/javascript" src="../leaflet.js"></script>
<script type="text/javascript" src="../d3.js"></script>
<style type="text/css">
#map{
width: 700px;
height: 600px;
}
</style>
</head>
<body>
<div id="map"></div>
<script type="text/javascript">
//
// LOAD THE MAP FROM MAPBOX & LEAFLET
//
var map = L.map("map").setView([50.0755,14.4378], 12);
mapLink = 'Mapbox';
L.tileLayer (
"link to mapbox",{
attribution:"© " + mapLink + " Contributors",
maxZoom:20,
}).addTo(map);
//
// Create the SVG layer on top of the map
//
L.svg().addTo(map);
// Create the standard variables selecting the SVG in the map element
var svg = d3.select("#map").append("svg");
var g = svg.append("g");
//Load the coordinate for the circle
var objects = [ {"circle":{"coordinates":[50.0755,14.4378]}}];
//Loop through to create a LatLng element that can be projected onto Leaflet map
for(var i = 0;i<objects.length;i++){
objects[i].LatLng = new L.LatLng(objects[i].circle.coordinates[0], objects[i].circle.coordinates[1])
};
//Create the circle object and store it in features
var feature = g.selectAll("circle")
.data(objects)
.enter()
.append("circle")
.style("fill", "red")
.attr("r", 20);
//Make the circle dynamic, by calling the update function whenever view is view is reset
map.on("viewreset", update)
//Call the update also on first load of the web page
update();
//Updates the position of the circle every time the map is updated
function update(){
feature.attr("transform",
function(d){
return "translate("+
map.latLngToLayerPoint(d.LatLng).x+","+
map.latLngToLayerPoint(d.LatLng).y+")";
})
};
</script>
</body>
</html>
As you note, your circle is appended:
But, it is invisible not because of opacity or color, but because you don't set the dimensions of the svg. With default dimensions of the svg, your circle is beyond its border and consequently hidden (it resides in the middle of the map, at [350,300], while the default size of an svg is 300x150 likely browser dependent). Try:
var svg = d3.select("#map")
.append("svg")
.attr("width",700)
.attr("height",600)
As your map is 700 pixels across and 600 pixels high.
I'm trying to use D3.js but I can not. I have the code below, but it does not print the map of Brazil. The screen does not show any errors, what could it be? My file "meso.json" is in topojSON format but it transforms the topojSON into GeoJson already in the d3.js code:
<html>
<head>
<title>D3.js</title>
<meta charset="utf-8" />
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/queue.v1.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 900,
height = 650;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geo.mercator()
.center([55,10])
.scale(750);
var path = d3.geo.path()
.projection(projection);
queue()
.defer(d3.json, "topo/meso.json")
.await(ready);
function ready(error, br_mesos){
if(error) return console.error(error);
var mesos = topojson.feature(br_mesos, br_mesos.objects.meso);
svg.append("path")
.datum(mesos)
.attr('d', path)
.attr('class', 'mesos');
}
</script>
</head>
<body>
</body>
</html>
Please change all your imports from http to https as some browsers block http.
for eg: change <script src="http://d3js.org/topojson.v1.min.js"></script>
to <script src="https://d3js.org/topojson.v1.min.js"></script> and similarly for all.
Is it possible to display a single state from a full US counties map?
I know I can get each states shape file and counties and create the topojson, but I'd rather work with one file if thats possible.
For example take Mike Bostock's full US county map.
US Counties Map
Would it be possible to show just NY from this?
I haven't seen any examples showing that type of functionality.
Once you've established your d3.json wrapper, you can use JavaScript filter method, so if your data has a'state' field:
d3.json(filename, function(error, data){ var single=data.filter(function(d){
return d.state==='Ohio';}
}
And then use the new single variable as your data for d3
I ended up working off of project to bounding box and created a clipping path around the state I wanted to display.
NY State W/ Counties - Clipped
I know this is a very late answer. However, it might be helpful for other people in the future.
This is an example of how to extract one state from the USA map file:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
fill: lightgray;
stroke: #000000;
stroke-width: 1.5;
}
path:hover {
fill:orange;
cursor:pointer;
}
#state-borders {
fill: white;
stroke: #000000;
stroke-width: 10.5px;
stroke-linejoin: round;
stroke-linecap: round;
pointer-events: none;
}
</style>
<body>
<script src="http://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 960,
height = 500,
centered;
var projection = d3.geoAlbersUsa()
.scale(1370)
.translate([width / 2, height / 2]);
var path = d3.geoPath()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("us-counties-github.json",function(json){
console.log(json);
svg.selectAll("path")
.attr("id", "state_fips")
.data(topojson.feature(json, json.objects.collection).features.filter(function(d) { return d.properties.state_fips == 36; }))
.enter()
.append("path")
.attr("d", path)
.attr("stroke","white")
.attr("fill", "gray");
});
</script>
*I downloaded the dataset from https://raw.githubusercontent.com/deldersveld/topojson/master/countries/united-states/us-albers-counties.json "the counties are included in this file".
Problem:
I'm attempting to create an interactive map of the US in which state, county and national boundaries are displayed. Counties are shaded based on data, and hovering over a state should highlight all counties in the state, and the state should be clickable. I want to achieve this by having a SVG with the county shapes inside of state shapes, inside of a US shape.
I can generate a county map based on a CENSUS county shape file, and I can shade the states based on data in an external CSV by prepping the file with TopoJSON command line and using the following code in D3:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 960,
height = 600;
var path = d3.geo.path()
.projection(d3.geo.albersUsa());
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("counties_pa.json", function(error, us) {
if (error) return console.error(error);
var color = d3.scale.threshold()
.domain([1, 10, 50, 100, 500, 1000, 2000, 5000])
.range(["#fff7ec", "#fee8c8", "#fdd49e", "#fdbb84", "#fc8d59", "#ef6548", "#d7301f", "#b30000", "#7f0000"]);
svg.append('g').attr('class','counties').selectAll("path").data(topojson.feature(us, us.objects.cb_2014_us_county_20m).features).enter().append('path').attr('d',path).attr('style',function(d){return 'fill:'+color(d.properties.population / d.properties.area * 2.58999e6);});
});
</script>
This is mostly visually acceptable (except it doesn't have discrete state / national boundaries) - but is functionally inadequate. In order to apply CSS to the counties on a state hover, the counties need to be within a state shape, or grouped somehow.
What i've tried:
Using topojson-merge in the command line to merge the counties into state shapes, and then render the state shapes separately - this helps with having discrete state borders - but I haven't figured a way to nest the counties into the respective state shapes.
What i'm working out now:
Somehow combining a state TopoJSON file and a county TopoJSON file and nesting the counties in the states, then rendering with D3.
Somehow using d3 to take non-nested state and county data and just nest it on the client on the client level.
In the end I would like to learn about the most effective and quickest rendering process to achieve my desired functionality.
Thanks for your help in advance.
I took a punt on your data sources, and here is what it looks like you're trying to achieve: http://bl.ocks.org/benlyall/55bc9474e6d531a1c1fe
Basically, I have generated a TopoJSON file using the following command line:
topojson -o counties_pa.json --id-property=+GEOID -p -e POP01.txt --id-property=+STCOU -p population=+POP010210D,area=ALAND,state=+STATEFP,county=+COUNTYFP cb_2014_us_county_20m.shp cb_2014_us_state_20m.shp
Some explanation on this:
-o counties_pa.json sets the name of the output file
--id-property=+GEOID will use that property in the input file as the id of each output geometry
-p means include all properties from the input file
-e POP01.txt will pull external data in from the file POP01.txt. This file is a csv file generated from the POP01.xls spreadsheet available from http://www.census.gov/support/USACdataDownloads.html#POP
--id-property=+STCOU means that the id property from the external file (POP01.txt) is in the STCOU column. This is used to match up with matching ids in the input file (which are in the GEOID property as explained above)
-p population=+POP010210D,area=ALAND,state=+STATEFP,county=+COUNTYFP explicitly lists the properties that I want in the output file, so anything extra won't be included. POP010210D is the column name for the population as at the 2010 census, so I just used that for demonstration purposes.
cb_2014_us_county_20m.shp cb_2014_us_state_20m.shp are the two input files. One for county shapes and one for state shapes. They will each be added to the output file in seperate properties named after their filenames.
I did it this way, as you seemed to be colouring your county areas based on population density, so both population and area needed to be in the output file. The population was pulled from the POP01 spreadsheet and linked to each county based on the GEOID (which is just the state number concatentated with the county number).
I was just looking for a quick and easy way to recreate your dataset, and then add the state boundaries to it so I could post the answer. Not sure how closely this matches your original data, but it seems to work for demonstration purposes.
From that, I took your code above and updated it to:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
}
path.state {
fill: none;
stroke: black;
stroke-width: .5px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 960,
height = 600;
var path = d3.geo.path()
.projection(d3.geo.albersUsa());
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("counties_pa.json", function(error, us) {
if (error) return console.error(error);
var color = d3.scale.threshold()
.domain([1, 10, 50, 100, 500, 1000, 2000, 5000])
.range(["#fff7ec", "#fee8c8", "#fdd49e", "#fdbb84", "#fc8d59", "#ef6548", "#d7301f", "#b30000", "#7f0000"]);
svg.append('g')
.attr('class','counties')
.selectAll("path")
.data(topojson.feature(us, us.objects.cb_2014_us_county_20m).features).enter()
.append('path')
.attr('d', path)
.attr("id", function(d) { return "county-" + d.id; })
.attr("data-state", function(d) { return d.properties.state; })
.attr('style',function(d) {
return 'fill:'+color(d.properties.population / d.properties.area * 2.58999e6);
})
.on("mouseover", hoverCounty)
.on("mouseout", outCounty);
svg.append('g')
.attr('class', 'states')
.selectAll("path")
.data(topojson.feature(us, us.objects.cb_2014_us_state_20m).features).enter()
.append("path")
.attr("class", "state")
.attr("id", function(d) { return "state-" + d.id; })
.attr("d", path);
});
function hoverCounty(county) {
d3.selectAll("path[data-state='" + county.properties.state + "']").style("opacity", .5);
}
function outCounty(county) {
d3.select(".counties").selectAll("path").style("opacity", null);
}
</script>
The new and interesting bits of code are:
Add a data-state attribute to each county to determine which state it belongs to:
.attr("data-state", function(d) { return d.properties.state; })
Add the state boundaries (I combined states to the TopoJSON file in the topojson command line)
svg.append('g')
.attr('class', 'states')
.selectAll("path")
.data(topojson.feature(us, us.objects.cb_2014_us_state_20m).features).enter()
.append("path")
.attr("class", "state")
.attr("id", function(d) { return "state-" + d.id; })
.attr("d", path);
});
Added hover handlers so you can see how I'm determining the grouping of counties into states:
function hoverCounty(county) {
d3.selectAll("path[data-state='" + county.properties.state + "']").style("opacity", .5);
}
function outCounty(county) {
d3.select(".counties").selectAll("path").style("opacity", null);
}
Tied these hover handlers to each county so they get executed at the appropriate times:
.on("mouseover", hoverCounty)
.on("mouseout", outCounty);
I tried a basic visualization with d3.js and dimple from udacity, however, I observe an effect I can't explain:
I run a simple dimple.js visualization which is embedded in an svg element which works fine.
When I preceed the svg element with a header, ... - see line 27
d3.select("body").append("h2").text("World Cup Attendance");
...the chart within the svg element seems to get translated and clipped at the bottom.
Why does adding this line alter the chart?
To my understanding the SVG element is an independent element which has its own relative coordinate system - not affected by preceding html elements...
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://dimplejs.org/dist/dimple.v2.1.2.min.js"></script>
<style>
circle.dimple-series-1 {
fill: red;
}
</style>
<script type="text/javascript">
function draw(data) {
/*
D3.js setup code
*/
"use strict";
var margin = 75,
width = 1400 - margin,
height = 600 - margin;
// this line moves the labels of the x axis
d3.select("body").append("h2").text("World Cup Attendance");
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin)
.attr("height", height + margin)
.append('g')
//.attr("transform", "translate(0,-30)")
.attr('class','chart');
var data = [
{ "year":"1980", "attendance":245000 },
{ "year":"1984", "attendance":245000 },
{ "year":"1988", "attendance":304400 }
];
/*
Dimple.js Chart construction code
*/
var myChart = new dimple.chart(svg, data);
var x = myChart.addTimeAxis("x", "year");
myChart.addMeasureAxis("y", "attendance");
x.dateParseFormat = "%Y";
x.tickFormat = "%Y";
// x.timeInterval = 4;
myChart.addSeries(null, dimple.plot.line);
myChart.addSeries(null, dimple.plot.scatter);
myChart.draw();
};
</script>
</head>
<body>
<script type="text/javascript">
/*
Use D3 (not dimple.js) to load the TSV file
and pass the contents of it to the draw function
*/
draw();
// d3.tsv("world_cup.tsv", draw);
</script>
</body>
</html>
I am going to guess it's because of this dimple method, "dimple._parentHeight". It calculates the height of the parent element of the svg and has a workaround for a firefox issue, so you could see a difference based on the container of the svg (body, in this instance) having or not having another element in it.
Your best bet (and what I've done for a similar reason) is to wrap the svg inside a div which will compute the height correctly :
var svg = d3.select("body")
.append("div")
.append("svg")