This commit changes the albersUsa projection so that it uses a custom projection
stream rather than a custom projection function. The projection stream passes
geometry to all three constituent projections, which are then clipped to their
respective viewports. The result is that geometry that appears in multiple
projections (such as a graticule) is now rendered correctly in each, with
clipping! In addition, the inverse projection is much easier to compute because
the viewport regions of each projection are explicitly defined.
This commit is contained in:
Mike Bostock 2013-05-19 11:38:22 -07:00
Родитель d1d71e1689
Коммит e8fcad0cfb
4 изменённых файлов: 107 добавлений и 119 удалений

89
d3.js поставляемый
Просмотреть файл

@ -3360,53 +3360,72 @@ d3 = function() {
return d3_geo_conic(d3_geo_conicEqualArea);
}).raw = d3_geo_conicEqualArea;
d3.geo.albersUsa = function() {
var lower48 = d3.geo.conicEqualArea().rotate([ 98, 0 ]).center([ 0, 38 ]).parallels([ 29.5, 45.5 ]);
var alaska = d3.geo.conicEqualArea().rotate([ 160, 0 ]).center([ 0, 60 ]).parallels([ 55, 65 ]);
var lower48 = d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]);
var alaska = d3.geo.conicEqualArea().rotate([ 160, 0, -35 ]).center([ 45, 44 ]).parallels([ 55, 65 ]);
var hawaii = d3.geo.conicEqualArea().rotate([ 160, 0 ]).center([ 0, 20 ]).parallels([ 8, 18 ]);
var puertoRico = d3.geo.conicEqualArea().rotate([ 60, 0 ]).center([ 0, 10 ]).parallels([ 8, 18 ]);
var alaskaInvert, hawaiiInvert, puertoRicoInvert;
function albersUsa(coordinates) {
return projection(coordinates)(coordinates);
}
function projection(point) {
var lon = point[0], lat = point[1];
return lat > 50 ? alaska : lon < -140 ? hawaii : lat < 21 ? puertoRico : lower48;
var y = point[0], x = point[1];
return (x > 50 ? alaska : y < -140 ? hawaii : lower48)(coordinates);
}
albersUsa.invert = function(coordinates) {
return alaskaInvert(coordinates) || hawaiiInvert(coordinates) || puertoRicoInvert(coordinates) || lower48.invert(coordinates);
var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k;
return (y >= .123 && y < .23 && x >= -.427 && x < -.215 ? alaska : y >= .16 && y < .23 && x >= -.215 && x < -.115 ? hawaii : lower48).invert(coordinates);
};
albersUsa.scale = function(x) {
albersUsa.stream = d3_geo_albersUsaStream([ lower48, alaska, hawaii ]);
albersUsa.scale = function(_) {
if (!arguments.length) return lower48.scale();
lower48.scale(x);
alaska.scale(x * .6);
hawaii.scale(x);
puertoRico.scale(x * 1.5);
lower48.scale(_);
alaska.scale(_ * .35);
hawaii.scale(_);
return albersUsa.translate(lower48.translate());
};
albersUsa.translate = function(x) {
albersUsa.translate = function(_) {
if (!arguments.length) return lower48.translate();
var dz = lower48.scale(), dx = x[0], dy = x[1];
lower48.translate(x);
alaska.translate([ dx - .4 * dz, dy + .17 * dz ]);
hawaii.translate([ dx - .19 * dz, dy + .2 * dz ]);
puertoRico.translate([ dx + .58 * dz, dy + .43 * dz ]);
alaskaInvert = d3_geo_albersUsaInvert(alaska, [ [ -180, 50 ], [ -130, 72 ] ]);
hawaiiInvert = d3_geo_albersUsaInvert(hawaii, [ [ -164, 18 ], [ -154, 24 ] ]);
puertoRicoInvert = d3_geo_albersUsaInvert(puertoRico, [ [ -67.5, 17.5 ], [ -65, 19 ] ]);
var k = lower48.scale(), x = +_[0], y = +_[1];
lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]);
alaska.translate([ x - .307 * k, y + .197 * k ]).clipExtent([ [ x - .427 * k, y + .123 * k ], [ x - .215 * k, y + .23 * k ] ]);
hawaii.translate([ x - .205 * k, y + .205 * k ]).clipExtent([ [ x - .215 * k, y + .16 * k ], [ x - .115 * k, y + .23 * k ] ]);
return albersUsa;
};
return albersUsa.scale(1e3);
return albersUsa.scale(1056);
};
function d3_geo_albersUsaInvert(projection, extent) {
var a = projection(extent[0]), b = projection([ .5 * (extent[0][0] + extent[1][0]), extent[0][1] ]), c = projection([ extent[1][0], extent[0][1] ]), d = projection(extent[1]);
var dya = b[1] - a[1], dxa = b[0] - a[0], dyb = c[1] - b[1], dxb = c[0] - b[0];
var ma = dya / dxa, mb = dyb / dxb;
var cx = .5 * (ma * mb * (a[1] - c[1]) + mb * (a[0] + b[0]) - ma * (b[0] + c[0])) / (mb - ma), cy = (.5 * (a[0] + b[0]) - cx) / ma + .5 * (a[1] + b[1]);
var dx0 = d[0] - cx, dy0 = d[1] - cy, dx1 = a[0] - cx, dy1 = a[1] - cy, r0 = dx0 * dx0 + dy0 * dy0, r1 = dx1 * dx1 + dy1 * dy1;
var a0 = Math.atan2(dy0, dx0), a1 = Math.atan2(dy1, dx1);
return function(coordinates) {
var dx = coordinates[0] - cx, dy = coordinates[1] - cy, r = dx * dx + dy * dy, a = Math.atan2(dy, dx);
if (r0 < r && r < r1 && a0 < a && a < a1) return projection.invert(coordinates);
function d3_geo_albersUsaStream(projections) {
return function(stream) {
var streams = projections.map(function(p) {
return p.stream(stream);
});
return {
point: function(x, y) {
streams.forEach(function(s) {
s.point(x, y);
});
},
sphere: function(x, y) {
streams.forEach(function(s) {
s.sphere();
});
},
lineStart: function(x, y) {
streams.forEach(function(s) {
s.lineStart();
});
},
lineEnd: function(x, y) {
streams.forEach(function(s) {
s.lineEnd();
});
},
polygonStart: function(x, y) {
streams.forEach(function(s) {
s.polygonStart();
});
},
polygonEnd: function(x, y) {
streams.forEach(function(s) {
s.polygonEnd();
});
}
};
};
}
var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = {

10
d3.min.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -6,13 +6,13 @@ import "geo";
// http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers
d3.geo.albersUsa = function() {
var lower48 = d3.geo.conicEqualArea()
.rotate([98, 0])
.center([0, 38])
.rotate([96, 0])
.center([-.6, 38.7])
.parallels([29.5, 45.5]);
var alaska = d3.geo.conicEqualArea()
.rotate([160, 0])
.center([0, 60])
.rotate([160, 0, -35])
.center([45, 44])
.parallels([55, 65]);
var hawaii = d3.geo.conicEqualArea()
@ -20,96 +20,65 @@ d3.geo.albersUsa = function() {
.center([0, 20])
.parallels([8, 18]);
var puertoRico = d3.geo.conicEqualArea()
.rotate([60, 0])
.center([0, 10])
.parallels([8, 18]);
var alaskaInvert,
hawaiiInvert,
puertoRicoInvert;
function albersUsa(coordinates) {
return projection(coordinates)(coordinates);
}
function projection(point) {
var lon = point[0],
lat = point[1];
return lat > 50 ? alaska
: lon < -140 ? hawaii
: lat < 21 ? puertoRico
: lower48;
var y = coordinates[0], x = coordinates[1];
return (x > 50 ? alaska : y < -140 ? hawaii : lower48)(coordinates);
}
albersUsa.invert = function(coordinates) {
return alaskaInvert(coordinates) || hawaiiInvert(coordinates) || puertoRicoInvert(coordinates) || lower48.invert(coordinates);
var k = lower48.scale(),
t = lower48.translate(),
x = (coordinates[0] - t[0]) / k,
y = (coordinates[1] - t[1]) / k;
return (y >= .123 && y < .230 && x >= -.427 && x < -.215 ? alaska
: y >= .160 && y < .230 && x >= -.215 && x < -.115 ? hawaii
: lower48).invert(coordinates);
};
albersUsa.scale = function(x) {
albersUsa.stream = d3_geo_albersUsaStream([lower48, alaska, hawaii]);
albersUsa.scale = function(_) {
if (!arguments.length) return lower48.scale();
lower48.scale(x);
alaska.scale(x * .6);
hawaii.scale(x);
puertoRico.scale(x * 1.5);
lower48.scale(_);
alaska.scale(_ * .35);
hawaii.scale(_);
return albersUsa.translate(lower48.translate());
};
albersUsa.translate = function(x) {
albersUsa.translate = function(_) {
if (!arguments.length) return lower48.translate();
var dz = lower48.scale(),
dx = x[0],
dy = x[1];
lower48.translate(x);
alaska.translate([dx - .40 * dz, dy + .17 * dz]);
hawaii.translate([dx - .19 * dz, dy + .20 * dz]);
puertoRico.translate([dx + .58 * dz, dy + .43 * dz]);
var k = lower48.scale(), x = +_[0], y = +_[1];
alaskaInvert = d3_geo_albersUsaInvert(alaska, [[-180, 50], [-130, 72]]);
hawaiiInvert = d3_geo_albersUsaInvert(hawaii, [[-164, 18], [-154, 24]]);
puertoRicoInvert = d3_geo_albersUsaInvert(puertoRico, [[-67.5, 17.5], [-65, 19]]);
lower48
.translate(_)
.clipExtent([[x - .455 * k, y - .238 * k], [x + .455 * k, y + .238 * k]]);
alaska
.translate([x - .307 * k, y + .197 * k])
.clipExtent([[x - .427 * k, y + .123 * k], [x - .215 * k, y + .230 * k]]);
hawaii
.translate([x - .205 * k, y + .205 * k])
.clipExtent([[x - .215 * k, y + .160 * k], [x - .115 * k, y + .230 * k]]);
return albersUsa;
};
return albersUsa.scale(1000);
return albersUsa.scale(1056);
};
function d3_geo_albersUsaInvert(projection, extent) {
var a = projection(extent[0]),
b = projection([.5 * (extent[0][0] + extent[1][0]), extent[0][1]]),
c = projection([extent[1][0], extent[0][1]]),
d = projection(extent[1]);
var dya = b[1]- a[1],
dxa = b[0]- a[0],
dyb = c[1]- b[1],
dxb = c[0]- b[0];
var ma = dya / dxa,
mb = dyb / dxb;
// Find center of circle going through points [a, b, c].
var cx = .5 * (ma * mb * (a[1] - c[1]) + mb * (a[0] + b[0]) - ma * (b[0] + c[0])) / (mb - ma),
cy = (.5 * (a[0] + b[0]) - cx) / ma + .5 * (a[1] + b[1]);
// Radial distance² from center.
var dx0 = d[0] - cx,
dy0 = d[1] - cy,
dx1 = a[0] - cx,
dy1 = a[1] - cy,
r0 = dx0 * dx0 + dy0 * dy0,
r1 = dx1 * dx1 + dy1 * dy1;
// Angular extent.
var a0 = Math.atan2(dy0, dx0),
a1 = Math.atan2(dy1, dx1);
return function(coordinates) {
var dx = coordinates[0] - cx,
dy = coordinates[1] - cy,
r = dx * dx + dy * dy,
a = Math.atan2(dy, dx);
if (r0 < r && r < r1 && a0 < a && a < a1) return projection.invert(coordinates);
// A naïve multi-projection stream.
// It probably only works because clipping buffers internally.
function d3_geo_albersUsaStream(projections) {
return function(stream) {
var streams = projections.map(function(p) { return p.stream(stream); });
return {
point: function(x, y) { streams.forEach(function(s) { s.point(x, y); }); },
sphere: function(x, y) { streams.forEach(function(s) { s.sphere(); }); },
lineStart: function(x, y) { streams.forEach(function(s) { s.lineStart(); }); },
lineEnd: function(x, y) { streams.forEach(function(s) { s.lineEnd(); }); },
polygonStart: function(x, y) { streams.forEach(function(s) { s.polygonStart(); }); },
polygonEnd: function(x, y) { streams.forEach(function(s) { s.polygonEnd(); }); }
};
};
}

Просмотреть файл

@ -334,13 +334,13 @@ suite.addBatch({
},
"coerces point radius to a number": {
"when the radius is specified as a constant": function(path) {
var p = path().context(testContext).pointRadius("6");
var p = path().projection(null).context(testContext).pointRadius("6");
assert.strictEqual(p.pointRadius(), 6);
p({type: "Point", coordinates: [0, 0]});
assert.strictEqual(testContext.buffer().filter(function(d) { return d.type === "arc"; })[0].r, 6);
},
"when the radius is specified as a function": function(path) {
var p = path().context(testContext).pointRadius(function() { return "6"; });
var p = path().projection(null).context(testContext).pointRadius(function() { return "6"; });
p({type: "Point", coordinates: [0, 0]});
assert.strictEqual(testContext.buffer().filter(function(d) { return d.type === "arc"; })[0].r, 6);
}