-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
Copy pathplot.js
176 lines (145 loc) · 5.55 KB
/
plot.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
var d3 = require('d3');
var Lib = require('../../lib');
var polygon = require('../../lib/polygon');
var getTopojsonFeatures = require('../../lib/topojson_utils').getTopojsonFeatures;
var locationToFeature = require('../../lib/geo_location_utils').locationToFeature;
var style = require('./style');
module.exports = function plot(gd, geo, calcData) {
for(var i = 0; i < calcData.length; i++) {
calcGeoJSON(calcData[i], geo.topojson);
}
function keyFunc(d) { return d[0].trace.uid; }
var gTraces = geo.layers.backplot.select('.choroplethlayer')
.selectAll('g.trace.choropleth')
.data(calcData, keyFunc);
gTraces.enter().append('g')
.attr('class', 'trace choropleth');
gTraces.exit().remove();
gTraces.each(function(calcTrace) {
var sel = calcTrace[0].node3 = d3.select(this);
var paths = sel.selectAll('path.choroplethlocation')
.data(Lib.identity);
paths.enter().append('path')
.classed('choroplethlocation', true);
paths.exit().remove();
// call style here within topojson request callback
style(gd, calcTrace);
});
};
function calcGeoJSON(calcTrace, topojson) {
var trace = calcTrace[0].trace;
var len = calcTrace.length;
var features = getTopojsonFeatures(trace, topojson);
for(var i = 0; i < len; i++) {
var calcPt = calcTrace[i];
var feature = locationToFeature(trace.locationmode, calcPt.loc, features);
if(!feature) {
calcPt.geojson = null;
continue;
}
calcPt.geojson = feature;
calcPt.ct = feature.properties.ct;
calcPt.index = i;
calcPt._polygons = feature2polygons(feature);
}
}
function feature2polygons(feature) {
var geometry = feature.geometry;
var coords = geometry.coordinates;
var loc = feature.id;
var polygons = [];
var appendPolygon, j, k, m;
function doesCrossAntiMerdian(pts) {
for(var l = 0; l < pts.length - 1; l++) {
if(pts[l][0] > 0 && pts[l + 1][0] < 0) return l;
}
return null;
}
if(loc === 'RUS' || loc === 'FJI') {
// Russia and Fiji have landmasses that cross the antimeridian,
// we need to add +360 to their longitude coordinates, so that
// polygon 'contains' doesn't get confused when crossing the antimeridian.
//
// Note that other countries have polygons on either side of the antimeridian
// (e.g. some Aleutian island for the USA), but those don't confuse
// the 'contains' method; these are skipped here.
appendPolygon = function(_pts) {
var pts;
if(doesCrossAntiMerdian(_pts) === null) {
pts = _pts;
} else {
pts = new Array(_pts.length);
for(m = 0; m < _pts.length; m++) {
// do nut mutate calcdata[i][j].geojson !!
pts[m] = [
_pts[m][0] < 0 ? _pts[m][0] + 360 : _pts[m][0],
_pts[m][1]
];
}
}
polygons.push(polygon.tester(pts));
};
} else if(loc === 'ATA') {
// Antarctica has a landmass that wraps around every longitudes which
// confuses the 'contains' methods.
appendPolygon = function(pts) {
var crossAntiMeridianIndex = doesCrossAntiMerdian(pts);
// polygon that do not cross anti-meridian need no special handling
if(crossAntiMeridianIndex === null) {
return polygons.push(polygon.tester(pts));
}
// stitch polygon by adding pt over South Pole,
// so that it covers the projected region covers all latitudes
//
// Note that the algorithm below only works for polygons that
// start and end on longitude -180 (like the ones built by
// https://github.com/etpinard/sane-topojson).
var stitch = new Array(pts.length + 1);
var si = 0;
for(m = 0; m < pts.length; m++) {
if(m > crossAntiMeridianIndex) {
stitch[si++] = [pts[m][0] + 360, pts[m][1]];
} else if(m === crossAntiMeridianIndex) {
stitch[si++] = pts[m];
stitch[si++] = [pts[m][0], -90];
} else {
stitch[si++] = pts[m];
}
}
// polygon.tester by default appends pt[0] to the points list,
// we must remove it here, to avoid a jump in longitude from 180 to -180,
// that would confuse the 'contains' method
var tester = polygon.tester(stitch);
tester.pts.pop();
polygons.push(tester);
};
} else {
// otherwise using same array ref is fine
appendPolygon = function(pts) {
polygons.push(polygon.tester(pts));
};
}
switch(geometry.type) {
case 'MultiPolygon':
for(j = 0; j < coords.length; j++) {
for(k = 0; k < coords[j].length; k++) {
appendPolygon(coords[j][k]);
}
}
break;
case 'Polygon':
for(j = 0; j < coords.length; j++) {
appendPolygon(coords[j]);
}
break;
}
return polygons;
}