Three.js - Using an SVG texture on SphereGeometry? - javascript

I've been following the tutorial here: http://www.delimited.io/blog/2015/5/16/interactive-webgl-globes-with-threejs-and-d3
This example uses a Canvas element and then wraps this as a texture to the THREE SphereGeometry. I wasn't happy with how blurry the lines were rendering so opted to render as an SVG. The SVG path data is being constructed by D3 and topojson.
My question is this: can I translate this SVG data to a SphereGeometry texture? I'd like to be able to zoom and retain detail on the final object - if there's a way to do this with canvas I'd also be interested.
Here's my code that draws the SVG;
var width = 1024,
height = 512;
var projection = d3.geo.equirectangular()
.scale(163)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("data/world.json", function(err, data) {
svg.append("path")
.datum(topojson.feature(data,data.objects.countries))
.attr("d", path)
.attr("class", "countries");

Related

d3.js alter projection on each iteration of the data loop

I'm very new to d3.js so my apologies if this is a stupid question.
When iterating over a geojson FeatureCollection list, i would like to change the projection on each item. is this possible?
My code looks something like this:
var width = 200,
var height = 200;
var svg = d3.select('#content g.map')
.append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geoAlbers()
.fitExtent(
[
[0, 0],
[width, height],
],
features
);
let geoGenerator = d3.geoPath().projection(projection)
var group = svg.append('g');
group.selectAll('path')
.data(geojson.features)
.enter()
.append('path')
.attr('d', geoGenerator)
.attr('stroke-width', '3')
.attr('stroke', 'black')
.attr('fill', 'none');
I'm using geoAlbers() with .fitExtend(), where my projection is drawn according to all elements in the geojson file. But i would like to draw it for each element independently.
My goal is to create a plot where each element in the array is the same size. Any help is appreciated!
You can use projection.fitSize or projection.fitExtent to modify a projection so that it will project a geojson feature to fill the specified bounding box. Normally these methods are used for a feature collection or a single feature, but there is no reason that we can't use it for each feature.
projection.fitSize/fitExtent only modify a projection's translate and scale values: they only zoom and pan the projected features. To make a grid using a conical projection like an Albers, you'll want to recalculate the projection's sequant lines/parallels for each feature or you may risk severe distortion in shapes. The use of a cylindrical projection removes this need (I've used a Mercator to simplify a solution). However, you should, in certain cases, calculate an appropriate anti-meridian for each feature so that no feature would span it, most d3 projections use 180 degrees east west as the default anti-meridian, however, you can change this for each projection by rotating the projection with projection.rotate(). The use of d3.geoBounds or d3.geoCenter could help facilitate this, my solution below does not account for these edge cases.
The snippet below uses g elements, one per feature, for positioning, then appends a path, and, by using selection.each(), calculates the projection parameters needed using projection.fitSize() so that the features bounding box is of the right size (each bounding box is size pixels square).
d3.json('https://unpkg.com/world-atlas#1/world/110m.json').then(function(world) {
let width = 960,
height = 500,
size = 40;
let projection = d3.geoMercator()
let path = d3.geoPath(projection);
let svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
let grid = svg.selectAll(null)
.data(topojson.feature(world, world.objects.countries).features)
.enter()
.append("g")
.attr("transform", function(d,i) {
return "translate("+[i%16*size,Math.floor(i/16)*size]+")";
})
let countries = grid.append("path")
.each(function(feature) {
projection.fitSize([size,size],feature)
d3.select(this).attr("d", path);
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>

How to calculate projection.scale() in D3.js for a fullscreen world map

I'm wondering how to calculate and determine a right value of projection.scale([value]) for a world map in full screen with D3.js.
The documentation says the default value is 150. But what does this "150" stand for?
I'm trying to draw a world map exact same width of the screen.
I set svg area to window.innerWidth but not sure what value I should give to the scale for the world map.
In some case people get the bounds of the feature they want to fit but I cannot get bounds of whole bounds of the world map.
What the right way to calculate the scale value in this case?
width = window.innerWidth;
height = window.innerHeight;
svg = d3.select("#svgArea").append("svg")
.attr("id", "svg")
.attr("width", width)
.attr("height", height);
projection = d3.geo.equirectangular()
.scale(????)
.translate([width / 2, height / 2])
.precision(.1);
path = d3.geo.path().projection(projection);
d3.json("world-50m.json", function(error, world) {
var land = topojson.feature(world, world.objects.land);
svg.insert("path", ".graticule")
.datum(land)
.attr("class", "land")
.attr("fill", "#aaaaaa")
.attr("stroke", "#000000")
.attr("stroke-width", "0.5px")
.attr("d", path);
})
Thanks in advance.

Creating a d3 map of nyc boroughs using JS and a geojson file

I've been trying to create a d3 map for weeks meshing tutorials with code. Here is my html code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript" src= "d3.min.js">
Modernizr.load({
test: Modernizr.svg && Modernizr.inlinesvg,
yep : [ 'd3.min.js',
'js/script.js' ]
});
</script>
</head>
<body>
<script src="NYC_MapInfo.geojson"></script>
<script>
var width = 960,
height = 1160;
var projection = d3.geo.mercator()
.scale(500)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("NYC_MapInfo.geojson", function(error, NYC_MapInfo) {
svg.append("path")
.datum(NYC_MapInfo.feature(NYC_MapInfo, NYC_MapInfo.objects))
.attr("d", path);
});
</script>
</body>
</html>
Here is the link to the json file:
Can someone please help me fix the code to load a image?
here is a link that might help you see the code: http://0.0.0.0:8000/nyc_zipmap.html
You're loading your geometry from GeoJSON file, so this is how you draw those polygons:
svg.selectAll("path")
.data(NYC_MapInfo.features)
.enter()
.append("path")
.attr("d", path);
What this code does it creates path for each single element found under "features" in your geojson file. Then uses the path function you already have to correctly draw them.
Once you do that, you'll find out that you cannot still see anything and that's because your map is centered on the wrong part of the world, so NYC is outside of your viewport. You can use d3 to find out exactly where you have to position your map, and how much it has to be zoomed. But that's bit more complicated. So quick solution is to zoom in your map little more.
var projection = d3.geo.mercator()
.scale(10000)
.translate([width / 2, height / 2]);
and then wait with creating your path function until the GeoJSON is loaded. Because then you can found center of it with d3.geo.centroid function.
var center = d3.geo.centroid(NYC_MapInfo);
projection.center(center);
var path = d3.geo.path().projection(projection);
The full code that displays NYC is:
<script>
var width = 960,
height = 1160;
var projection = d3.geo.mercator()
.scale(10000)
.translate([width / 2, height / 2]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("NYC_MapInfo.geojson", function(error, NYC_MapInfo) {
// after loading geojson, use d3.geo.centroid to find out
// where you need to center your map
var center = d3.geo.centroid(NYC_MapInfo);
projection.center(center);
// now you can create new path function with
// correctly centered projection
var path = d3.geo.path().projection(projection);
// and finally draw the actual polygons
svg.selectAll("path")
.data(NYC_MapInfo.features)
.enter()
.append("path")
.attr("d", path);
});
</script>
You might need to fiddle with projection center and scale to display it properly.
Look into this thread for more details how to do that with code.

Polygons formatted in GeoJSON don't appear correctly

I'm trying to make a map using d3.js and three data files:
a simple .topojson file with my base map
a .geojson file with polygonal areas
a .csv file with data (which has a field in common with the .geojson)
First, I created my base map with this code:
var width = 360,
height = 600,
width2 = 479;
var proj = d3.geo.mercator()
.center([7.76, 48.57])
.scale(130000)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(proj);
var svg = d3.select("#carte").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("strasbourg.json", function(error, base) {
svg.append("path")
.data(topojson.feature(base, base.objects.contour).features)
.attr("class", "fond-stras")
.attr("d", path);
The result was fine (http://raphi.m0le.net/data/BAS/App_d3_strasrk/etape%201.html), so I made my code more complex:
var width = 360,
height = 600,
width2 = 479;
var proj = d3.geo.mercator()
.center([7.76, 48.57])
.scale(130000)
.translate([width / 2, height / 2]);
// This array will be used to the next choropleth
var couleurs = ['rgb(165,15,21)','rgb(222,45,38)','rgb(251,106,74)',
'rgb(252,146,114)','rgb(252,187,161)','rgb(254,229,217)'];
var path = d3.geo.path()
.projection(proj);
var svg = d3.select("#carte").append("svg")
.attr("width", width)
.attr("height", height);
// I use queue.v1.min.js to load my .topojson, .geojson and .csv
queue()
.defer(d3.json,"strasbourg.json")
.defer(d3.json, "chute_ries.json")
.defer(d3.csv, "progression_riesk.csv")
.await(ready);
function ready(error,base,areas,results) {
// I declare my base map like before, it works always fine
svg.append("path")
.data(topojson.feature(base, base.objects.contour).features)
.attr("class", "fond-stras")
.attr("d", path);
// the problematic part begins here :
svg.append("g").attr("class","areas")
.selectAll("path")
.data(areas.features)
.enter()
.append("path")
.attr("class", "area")
.attr("d", path)
// I write this to test an automatic colouring
.attr("fill", function(d,i){
if (results[i].diff_ries<-23){
return couleurs[0]
}
else {return couleurs[4]
}
})
Unfortunately, it doesn't work as you will see here: http://raphi.m0le.net/data/BAS/App_d3_strasrk/etape%202.html
Despite my efforts, I have not been able to get this to work. My .geojson was created with QGis, so I guess that it respects the Right-Hand-Rule.
My console does not display any errors so I haven't been able to determine what is wrong. I suspect that it could be the data() declaration, but I've seen several examples where it was written like this with .geojson data and worked perfectly.
It was a little tricky, but the problem comes from the projection of my original file : Lambert-93 (a french reference) instead of the Mercator precised in the script !
I resaved the file with a Mercator projection, and all worked perkectly fine !

Drawing Kenyan counties d3.geo.path()

I'm a bit of a newb with D3 and I'm trying to work on a project for the organization I work for. I need to draw a choropleth map of Kenya with some data we collected. I'm working off Scott Murray's book Interactive Data Visualization for the Web. In his book he uses the following to generate paths from a json file of US States:
//Width and height
var w = 500;
var h = 300;
//Define default path generator
var path = d3.geo.path();
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Load in GeoJSON data
d3.json("us-states.json", function(json) {
//Bind data and create one path per GeoJSON feature
svg.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path);
});
I tried adapting this code to draw Kenyan counties from a json file I created from the Kenya shapefile I downloaded. The structure of the json file looks just like that of the US states file but when I look at the HTML in a browser I don't see any lines. I check the console and the path placeholders are there there is no data. If I swap in the US-states.json file I see the paths with data and the map in the browser.
Can someone help me please.
Thanks
I am doing something similar for Nairobi. As Lars has said in the comments it looks like you haven't set a projection for the map. The code below uses a mercator projection and the map is centered on Nairobi.
(Note that the scale is very zoomed and you would have to decrease this to get the whole of Kenya in).
var margin = {top: 50, right: 20, bottom: 20, left: 60},
dynwidth = $("#nairobistock").width(),
rowheight = 460;
width = dynwidth - margin.left - margin.right,
height = rowheight - margin.top - margin.bottom;
var projection = d3.geo.mercator()
.center([36.8, -1.3])
.scale([90000])
.translate([width/2, height/2]);
var nairobipathing = d3.geo.path().projection(projection);
var svg = d3.select("#nairobistock").append("svg")
.attr("width", (width + margin.left + margin.right) )
.attr("height", (height + margin.top + margin.bottom) );
d3.json("topojson/KEN-3topo.json", function(error, nairobi){
if (error) return console.error(error);
console.log(nairobi);
console.log("topojson added");
svg.selectAll("path")
.data(topojson.feature(nairobi, nairobi.objects.suburbs).features)
.enter()
.append("path")
.attr("class", function(d) {return d.ID;})
.attr("d", nairobipathing );
});
Hope this helps.

Categories