d3 / d3-geo Goto Github PK
View Code? Open in Web Editor NEWGeographic projections, spherical shapes and spherical trigonometry.
Home Page: https://d3js.org/d3-geo
License: Other
Geographic projections, spherical shapes and spherical trigonometry.
Home Page: https://d3js.org/d3-geo
License: Other
In a few cases I've encountered geoPath fill errors, with country fills being inappropriately inverted.
Below is an example that replicates the errors in d3 v4.2.1 and Chrome 51.0.2704.103 (Mac OS). The example assumes world-50m.json
is on the relative path. Also, see comments below for more.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
.land {
fill: #222;
}
.boundary {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
</style>
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 960,
height = 500;
var projection = d3.geoMercator()
.scale(150)
.translate([width / 2, height / 2])
.center([-1, 0]);
// This projection also exhibits fill errors
// when used with world-110m.json
// var projection = d3.geoAzimuthalEqualArea()
// .scale(150)
// .translate([width / 2, height / 2])
// .rotate([0, 71, 0]);
var path = d3.geoPath()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("world-50m.json", function(error, world) {
if (error) throw error;
svg.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
</script>
In the geoCircle PR discussion, you talked about pulling geoProjection and the rest of d3.v3.geo into d3-geo for d3 v4.0, and then breaking them out into d3-geo-projection for v5.0 when we do the new graphics pipeline.
I took a dive through the code, and found that geo.projection and geo.path have a circular dependency between them, and that geo.path has a dependency on the albers-usa projection (which then depends on other projections, etc.). It looks like the only way to migrate these over is to move geo.path, geo.projection and the basic projections over all at once (along with the clip-*
and path-*` components). If this sounds like the right thing to do, I can get started on doing this all locally this week. (The eventual PR will have to wait for geoArea, geoCentroid and geoBounds to be merged)
Also, I've got a couple questions regarding the desire to eventually break d3.geoProjection into its own module. I understand the value of the d3-geo functions as tools for doing spherical math, but without projections, no one will be able to render maps. Do you see a lot of use cases where developers might import d3-geo
and not need d3-geo-projection
?
The link to "The Toolmaker’s Guide" gives a video not found on Vimeo.
Same for the FOSS4G link in https://bost.ocks.org/mike/#talks
We need ’em to derive related projections. For example, Goode’s interrupted Mollweide needs the raw Mollweide projection. And the Berghaus projection needs the azimuthal equidistant projection, etc.
Not a fan of this API. Maybe we can replace it by moving projection.clipExtent to path.clipExtent?
I've encountered some buggy behavior using the gnomonic projection with customized graticule steps. In some cases, there is incorrect line clipping. In others, an error is thrown. The issues arose when using a custom graticule step of [15, 15]
. Removing the custom step or adjusting the projection clipAngle can make the issues to go away.
Below is an example that replicates the errors in d3 v4.2.1 and Chrome 51.0.2704.103 (Mac OS). The example assumes world-50m.json
is on the relative path. Also, see comments below for more.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
.land {
fill: #222;
}
.boundary {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
</style>
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 960,
height = 600;
var projection = d3.geoGnomonic()
.scale(150)
.translate([width / 2, height / 2])
.rotate([-105, 0, 0]); // comment this line: no error, but bad line crossing
// .clipAngle(90 - 1e-3); // uncomment this line, errors do not occur
var path = d3.geoPath()
.projection(projection);
var graticule = d3.geoGraticule()
.step([15, 15]); // comment this line, errors do not occur
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
d3.json("world-50m.json", function(error, world) {
if (error) throw error;
svg.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
</script>
It would be great to ditch this API, but maybe that should wait for 5.0…
As shown here: https://www.jasondavies.com/maps/two-point-equidistant/
So at this point I've gone ahead and made speculative branches for the components that are listed in the issues (circle, area, centroid, bounds). I'm sure those will take some time to review, revise and merge, so in the meantime I've also looked ahead at the parts of d3.geo
(v3) that are not included in the issues for this repo (or the scaffold of the README).
It seems to me that d3.geo.path
, d3.geo.projection
and the "batteries included" projections (like Albers) are being slated for the d3-geo-projection
module (let me know if I've got any of this wrong). In v4 (or whenever it's ready) will d3-geo-projection
become a first-class part of D3? Or is the intention to keep it as an optional/auxiliary module?
I forked d3-geo-projection
just to take a look at its structure, but I'm holding off on getting to work until I better understand our goals.
To match the new behavior of d3-geo-projection.
It’s the same number of characters, but it might be a little more obvious to have a method that generates the default graticule geometry object directly, rather than constructing a default graticule generator, and then using it only once.
d3.geoGraticule10 = function() {
return d3.geoGraticule()();
};
Some projections don’t behave well with the default antimeridian clipping. Even those that do sometimes have other default clip settings that make more sense—like orthographic should default to a 90° clip circle for the front hemisphere.
Not a big fan of this API, either.
Slightly more concise to say:
var path = d3.geoPath(projection);
Than to say:
var path = d3.geoPath().projection(projection);
They were fun though.
It would be nice to update these to d3-geo 1.1+ and release them as a D3 module:
https://github.com/mbostock/d3/blob/master/src/geo/stream.js
Needed by d3.geoLength #1 and d3.geoArea #4. Will also be needed by d3-geo-projection.
The geo tile plugin would be a stellar addition in the 4.0 world.
Related: d3/d3#486
I stumbled upon an error while trying to use d3.geoBounds, and while trying to understand the cause of it I found something that is most likely a bug in area.js
Here's the part of the code I think is causing the error
export var areaRingSum;
var areaSum,
lambda00,
phi00,
lambda0,
cosPhi0,
sinPhi0;
export var areaStream = {
point: noop,
lineStart: noop,
lineEnd: noop,
polygonStart: function() {
areaRingSum.reset(); // <<< areaRingSum was never initialized
areaStream.lineStart = areaRingStart;
areaStream.lineEnd = areaRingEnd;
},
polygonEnd: function() {
var areaRing = +areaRingSum;
areaSum.add(areaRing < 0 ? tau + areaRing : areaRing);
this.lineStart = this.lineEnd = this.point = noop;
},
sphere: function() {
areaSum.add(tau);
}
}
And here's the error
Cannot read property 'reset' of undefined
at Object.areaStream.polygonStart (...\node_modules\d3-geo\build\d3-geo.js:169:18)
at Object.boundsStream.polygonStart (...\node_modules\d3-geo\build\d3-geo.js:278:18)
at streamPolygon (...\node_modules\d3-geo\build\d3-geo.js:143:12)
at Object.streamGeometryType.MultiPolygon (...\node_modules\d3-geo\build\d3-geo.js:126:23)
at streamGeometry (...\node_modules\d3-geo\build\d3-geo.js:88:40)
at Object.streamObjectType.Feature (...node_modules\d3-geo\build\d3-geo.js:94:7)
at geoStream (...\node_modules\d3-geo\build\d3-geo.js:150:36)
at Object.bounds [as geoBounds] (...\node_modules\d3-geo\build\d3-geo.js:400:5)
I think it's worth noting that d3.geoCentroid and d3.geoLength all work on the same GeoJson object that causes the error when passed to d3.geoBounds
Also, d3.geoBounds returns [NaN, NaN] when a point is passed instead of a multipolygon, but I don't know the cause of this.
why? because the D3/4 core build has no sane map projection for making a simple world map.
and no, plate carrée and mercator don't count.
let's just add geoNaturalEarth, or really like any other decent looking pseudo-cylindrical map projection.
not all of them, just one, so people can make better world maps out of the box.
thanks!
It’d be nice to have a simple projection that only does scale and translate (and possibly inverts y), so that you can use path.fitExtent or path.fitSize in conjunction with pre-projected TopoJSON.
You can resize manually with d3.geoTransform, but it’s a pain to compute the scale yourself.
See also #33 regarding projection.clipExtent.
I'm trying to resize a preprojected geojson:
geoproject 'd3.geoIdentity().fitExtent([[0,0],[960,500]], d)'
… which leads to a vertically flipped output because in the original coordinate system, the origin is at the bottom left instead of the top. Using an extent of [[0,500],[960,0]]
didn't help (that just rotated everything by 180°).
With the old topojson I would just do topojson --width=960 --height=500
. Am I missing something?
D3 4.0 renamed circle.angle to circle.radius. I'm having trouble understanding the concepts, so I hope someone can enlighten me.
The documentation currently says (emphasis mine):
circle.radius([radius])
If radius is specified, sets the circle radius to the specified angle in degrees, and returns this circle generator. The radius may also be specified as a function; this function will be invoked whenever a circle is generated, being passed any arguments passed to the circle generator. If radius is not specified, returns the current radius accessor, which defaults to:
In the source code, it appears the radius
variable is passed into cos()
and sin()
, which has to be an angle.
This is more of a question. I noticed that the geo path pointRadius()
getter returns:
If I recall correctly, in a fair share of the other D3 modules, similar accessors will always return an accessor function, even if it just wraps a constant return value.
Understanding that changing this would be breaking, I simply wondered, if there is a rationale for it, which is specific to the d3-geo applications?
I wonder if it would be faster to not reassign listener methods, since I expect that dynamically assigning methods makes it harder for the runtime to optimize.
The AlbersUsa.js file says that the default scale is 1285 in the infile comments, but is actually 1070 in the code.
I am going over updating the TS definitions for d3-geo 1.3.
With the changes to the geoPath
API, it seems that when using geoPath()
or geoPath(someProjection)
the defaults are not set internally for omitted arguments.
I.e. an omitted project and/or context, will return undefined
when using the corresponding getter on the returned geo path generator.
As I am trying to breeze through the definitions update, you might be faster than me to create a quick PR for this repo. Otherwise, I will circle back to it asap. 😄
It would be nice to have tests that take world-110m.json as input and verify compatibility with D3 3.x.
I'm trying to use the geoAlbersUsa projection, but this code returns an error.
Code:
import { geoAlbersUsa } from 'd3-geo';
const projection = geoAlbersUsa()
const coords = projection([37, -96]) || [];
Error:
d3-geo.js:2216 Uncaught TypeError: Cannot read property 'stream' of undefined
which is pointing to this line: https://github.com/d3/d3-geo/blob/master/src/projection/index.js#L13
and this file: https://github.com/d3/d3-geo/blob/a36f5cac465003ba8b6ceeee3834b1cef2b3a5f5/src/transform.js
I believe (though I could very well be using this incorrectly) that there's an issue with the function scoping, leading this
to be undefined.
I was trying to implement a bubble map chart in React, using the example of Mercator projection. However, I got errors about cannot find geoMercator all the time. I imported d3 by npm but got the error of function missing. I tried d3-geo, and d3-geo-projection but none of them works. As a newbie of React any help is appreciated!
Error:
WorldMap.js:61 Uncaught (in promise) TypeError: _d2.default.geoMercator is not a function
Code:
`import $ from 'jquery';
import ReactFauxDOM from 'react-faux-dom';
import firebase from 'firebase';
import React from 'react';
import d3 from 'd3';
import d3geo from 'd3-geo-projection';
import topojson from 'topojson';
class WorldMap extends React.Component {
constructor(props) {
super()
}
render(){
var width = 960,
height = 600;
var projection = d3.geoMercator()
.scale((width - 3) / (2 * Math.PI))
.translate([width / 2, height / 2]);
var path = d3.geoPath()
.projection(projection);
var graticule = d3.geoGraticule();
var svgNode = ReactFauxDOM.createElement('div');
var svg = d3.select(svgNode).append("svg")
.attr("width", width)
.attr("height", height);
svg.append("defs").append("path")
.datum({ type: "Sphere" })
.attr("id", "sphere")
.attr("d", path);
svg.append("use")
.attr("class", "stroke")
.attr("xlink:href", "#sphere");
svg.append("use")
.attr("class", "fill")
.attr("xlink:href", "#sphere");
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
d3.json("./maptest.json", function(error, world) {
if (error) throw error;
svg.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path)
.style("fill", "gray");
svg.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) {
return a !== b; }))
.attr("class", "boundary")
.attr("d", path)
.style("fill", "gray");
});
svg.selectAll("circle")
.data(this.cases)
.enter().append("circle", ".pin")
.attr("r", 2)
.attr("transform", function(d) {
return "translate(" + projection([
d.longitude,
d.latitude
]) + ")";
})
.style("fill", "steelblue");
return svgNode.toReact();
}
}
export default WorldMap;
`
I think I've found an error dealing with projection.fitSize
and path.bounds
. Here's a block that reproduces it: https://bl.ocks.org/gabrielflorit/bb7ed6a56dbd5f66ee8c06b88a2b4f09
Specifically, accessing the bounds
before calling fitSize
makes the projection fit incorrectly:
// Filter US to lower 48 + AK + HI
var conus = topojson.feature(us, {
type: 'GeometryCollection',
geometries: us.objects.states.geometries.filter(function (d) { return d.id < 60; }),
})
// Create projection
var projection = d3.geoAlbersUsa()
// Create path
var path = d3.geoPath().projection(projection)
// Comment-in the following line and the projection gets fitted
// incorrectly.
var bounds = path.bounds(conus)
// Fit projection in half the container.
projection.fitSize([width/2, height/2], conus)
svg.append('path')
.datum(conus)
.attr('d', path)
All of the examples I've been able to find (e.g. https://mbostock.github.io/d3/talk/20111018/azimuthal.html) use the origin method to set the map origin. however this method is no longer present in d3-geo (https://github.com/d3/d3-geo/search?utf8=%E2%9C%93&q=origin) nor is it clear how to replace it. maybe this isn't a legit bug but it's got me banging my head on the wall, seems like the docs should talk about it.
Apologies in advance if I've misunderstood something or logged this issue in the wrong place!
I've built numerous pan-and-zoom maps before, all of which used either the mercator
or albers
projections (I should also point out at this point that I'm using v3, not v4, of d3): https://github.com/d3/d3-3.x-api-reference/blob/master/Geo-Projections.md
I'm now trying to build a US map, and switched to the albersUsa
projection. All of a sudden, my map code is giving me lots of errors, and it suggests that there is a missing method in the albersUsa projection?
this.projection = d3.geo.albers() // `albers` is ok, `albersUsa` is not!
.scale(this.mapModel.get('mapScale'))
.translate(this.mapModel.get('translate'));
// ...a while later:
let bounds = [0,0]; // just for this example
const coords = this.projection(bounds);
In the albers projection, coords
comes out as [2065.4195284613525, 73.63503703113963]
. But if I switch to albersUsa
, coords
comes out as null
.
Any advice would be much appreciated! Thanks
The compiler complains that contextStream doesn't have pointRadius property.
PathString and PathContext should apparently be instantiated without new, since they return the created objects.
https://github.com/d3/d3-geo/blob/master/src/path/index.js
path.context = function(_) {
if (!arguments.length) return context;
contextStream = (context = _) == null ? new PathString : new PathContext(_);
if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius);
return path;
};
Any interest in adding a projection.contain()
method that projects to a bounding box a la http://bl.ocks.org/mbostock/4707858?
I think it's a pretty common use case to start with a bounding box rather than a desired scale/translate, and the alternative is to fiddle with values until it looks nice.
I took a crack at it, mostly seems to work except I need to figure out why it doesn't work for projections with clipExtents:
https://github.com/veltman/d3-geo/blob/master/src/projection/index.js#L81-100
In terms of naming, I chose contain
because that's the CSS background-size
nomenclature, but it could also be something like fitTo
.
This would break the getter/setter pattern a bit. Alternatively, it could be a more consistent separate method like:
var projection = ...;
var fitter = d3.geoFit()
.projection(projection)
.width(1280)
.height(720)
.scale(0.9);
// Change
fitter.scale(1);
Although that seems kind of verbose. Any thoughts?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.