From f462becbbfe87228b29bb8db7cd64cd3713a729c Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Thu, 28 Nov 2024 15:52:04 -0500 Subject: [PATCH 01/11] uninstall sane-topojson --- package-lock.json | 7 ------- package.json | 1 - 2 files changed, 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index a754ff15502..6af563361d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -113,7 +113,6 @@ "raw-loader": "^4.0.2", "read-last-lines": "^1.8.0", "run-series": "^1.1.9", - "sane-topojson": "^4.0.0", "sass": "^1.78.0", "stream-browserify": "^3.0.0", "through2": "^4.0.2", @@ -8914,12 +8913,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "node_modules/sane-topojson": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/sane-topojson/-/sane-topojson-4.0.0.tgz", - "integrity": "sha512-bJILrpBboQfabG3BNnHI2hZl52pbt80BE09u4WhnrmzuF2JbMKZdl62G5glXskJ46p+gxE2IzOwGj/awR4g8AA==", - "dev": true - }, "node_modules/sass": { "version": "1.78.0", "resolved": "https://registry.npmjs.org/sass/-/sass-1.78.0.tgz", diff --git a/package.json b/package.json index c76559d56bd..3167cac8007 100644 --- a/package.json +++ b/package.json @@ -172,7 +172,6 @@ "raw-loader": "^4.0.2", "read-last-lines": "^1.8.0", "run-series": "^1.1.9", - "sane-topojson": "^4.0.0", "sass": "^1.78.0", "stream-browserify": "^3.0.0", "through2": "^4.0.2", From 7c407ec427e504b97f1d957fc1161da6f33e5337 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Wed, 4 Dec 2024 17:50:52 -0500 Subject: [PATCH 02/11] install sane-topojson with dist/un.json --- package-lock.json | 6 ++++++ package.json | 1 + 2 files changed, 7 insertions(+) diff --git a/package-lock.json b/package-lock.json index 6af563361d3..d8da2d02b6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,6 +53,7 @@ "regl-line2d": "^3.1.3", "regl-scatter2d": "^3.3.1", "regl-splom": "^1.0.14", + "sane-topojson": "github:etpinard/sane-topojson#c76e75396879a1a8d96e37237383b3ca5305afc6", "strongly-connected-components": "^1.0.1", "style-loader": "^4.0.0", "superscript-text": "^1.0.0", @@ -8913,6 +8914,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sane-topojson": { + "version": "4.0.0", + "resolved": "git+ssh://git@github.com/etpinard/sane-topojson.git#c76e75396879a1a8d96e37237383b3ca5305afc6", + "integrity": "sha512-hr+22pMxedo7f2Gogz7yNHlYlVQBEuIxSvJnM/WSbOm6iuF/2BcYHhtlqWoLmMHMael31NLiVymFzy31c+JwbQ==" + }, "node_modules/sass": { "version": "1.78.0", "resolved": "https://registry.npmjs.org/sass/-/sass-1.78.0.tgz", diff --git a/package.json b/package.json index 3167cac8007..6507cb32dc3 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "regl-line2d": "^3.1.3", "regl-scatter2d": "^3.3.1", "regl-splom": "^1.0.14", + "sane-topojson": "github:etpinard/sane-topojson#c76e75396879a1a8d96e37237383b3ca5305afc6", "strongly-connected-components": "^1.0.1", "style-loader": "^4.0.0", "superscript-text": "^1.0.0", From 8b5de735e2e99753da36dfa4e11d2c46b962e2c7 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Tue, 3 Dec 2024 19:46:48 -0500 Subject: [PATCH 03/11] use un_world from sane-topojson --- src/lib/topojson_utils.js | 10 +++++++++- src/plots/geo/constants.js | 15 +++++++++------ src/plots/geo/layout_attributes.js | 2 +- test/plot-schema.json | 3 ++- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/lib/topojson_utils.js b/src/lib/topojson_utils.js index b533b1a71bf..a00f703131a 100644 --- a/src/lib/topojson_utils.js +++ b/src/lib/topojson_utils.js @@ -13,7 +13,15 @@ topojsonUtils.getTopojsonName = function(geoLayout) { }; topojsonUtils.getTopojsonPath = function(topojsonURL, topojsonName) { - return topojsonURL + topojsonName + '.json'; + var path = topojsonURL; + + if(topojsonName.startsWith('un_')) { + path += 'un'; + } else { + path += topojsonName; + } + + return path + '.json'; }; topojsonUtils.getTopojsonFeatures = function(trace, topojson) { diff --git a/src/plots/geo/constants.js b/src/plots/geo/constants.js index ad130053460..16a04e4339e 100644 --- a/src/plots/geo/constants.js +++ b/src/plots/geo/constants.js @@ -141,14 +141,17 @@ exports.lataxisSpan = { '*': 180 }; +var world = { + lonaxisRange: [-180, 180], + lataxisRange: [-90, 90], + projType: 'equirectangular', + projRotate: [0, 0, 0] +}; + // defaults for each scope exports.scopeDefaults = { - world: { - lonaxisRange: [-180, 180], - lataxisRange: [-90, 90], - projType: 'equirectangular', - projRotate: [0, 0, 0] - }, + un: world, + world: world, usa: { lonaxisRange: [-180, -50], lataxisRange: [15, 80], diff --git a/src/plots/geo/layout_attributes.js b/src/plots/geo/layout_attributes.js index b94cca3bec5..286766dd65f 100644 --- a/src/plots/geo/layout_attributes.js +++ b/src/plots/geo/layout_attributes.js @@ -104,7 +104,7 @@ var attrs = module.exports = overrideAll({ scope: { valType: 'enumerated', values: sortObjectKeys(constants.scopeDefaults), - dflt: 'world', + dflt: 'un', description: 'Set the scope of the map.' }, projection: { diff --git a/test/plot-schema.json b/test/plot-schema.json index 6aa77cf3338..22eb7e2075e 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -2540,7 +2540,7 @@ "role": "object", "scope": { "description": "Set the scope of the map.", - "dflt": "world", + "dflt": "un", "editType": "plot", "valType": "enumerated", "values": [ @@ -2549,6 +2549,7 @@ "europe", "north america", "south america", + "un", "usa", "world" ] From 854cb8cd43a265c94dfd82cbd906a483cabd31e1 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Fri, 27 Dec 2024 11:59:18 -0500 Subject: [PATCH 04/11] collect LineStrings for un dataset --- src/lib/geo_location_utils.js | 12 +++++++++++- src/plots/geo/layout_defaults.js | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/geo_location_utils.js b/src/lib/geo_location_utils.js index e15659f8f45..b45f02b987a 100644 --- a/src/lib/geo_location_utils.js +++ b/src/lib/geo_location_utils.js @@ -63,6 +63,13 @@ function locationToFeature(locationmode, location, features) { for(i = 0; i < filteredFeatures.length; i++) { f = filteredFeatures[i]; if(f.id === locationId) return f; + if( + f.properties && + f.properties.iso3cd === locationId + ) { + // TODO: we may need to collect all the polygons instead? + return f; + } } loggers.log([ @@ -77,7 +84,7 @@ function locationToFeature(locationmode, location, features) { function feature2polygons(feature) { var geometry = feature.geometry; var coords = geometry.coordinates; - var loc = feature.id; + var loc = feature.properties.iso3cd || feature.id; var polygons = []; var appendPolygon, j, k, m; @@ -173,6 +180,9 @@ function feature2polygons(feature) { appendPolygon(coords[j]); } break; + case 'LineString': + appendPolygon(coords); + break; } return polygons; diff --git a/src/plots/geo/layout_defaults.js b/src/plots/geo/layout_defaults.js index a5d64e2e9f8..3216a6c67dd 100644 --- a/src/plots/geo/layout_defaults.js +++ b/src/plots/geo/layout_defaults.js @@ -33,7 +33,7 @@ function handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce, opts) { // no other scopes are allowed for 'albers usa' projection if(isAlbersUsa) scope = geoLayoutOut.scope = 'usa'; - var isScoped = geoLayoutOut._isScoped = (scope !== 'world'); + var isScoped = geoLayoutOut._isScoped = (scope !== 'world' && scope !== 'un'); var isSatellite = geoLayoutOut._isSatellite = projType === 'satellite'; var isConic = geoLayoutOut._isConic = projType.indexOf('conic') !== -1 || projType === 'albers'; var isClipped = geoLayoutOut._isClipped = !!constants.lonaxisSpan[projType]; From 1a60af5baeb2e3635413fc40344df39dbec2b444 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Fri, 27 Dec 2024 12:54:23 -0500 Subject: [PATCH 05/11] TMP link until un is published to CDN --- src/lib/topojson_utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/topojson_utils.js b/src/lib/topojson_utils.js index a00f703131a..a0e90a45bac 100644 --- a/src/lib/topojson_utils.js +++ b/src/lib/topojson_utils.js @@ -17,6 +17,7 @@ topojsonUtils.getTopojsonPath = function(topojsonURL, topojsonName) { if(topojsonName.startsWith('un_')) { path += 'un'; + return 'https://raw.githubusercontent.com/etpinard/sane-topojson/refs/heads/un-borders/dist/un.json'; } else { path += topojsonName; } From 03a6b84668fd9800de4b2e006762c4ce659b1574 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Fri, 27 Dec 2024 13:02:18 -0500 Subject: [PATCH 06/11] fix empty --- src/traces/scattergeo/plot.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/traces/scattergeo/plot.js b/src/traces/scattergeo/plot.js index 9124900e8b3..4abbdcbe937 100644 --- a/src/traces/scattergeo/plot.js +++ b/src/traces/scattergeo/plot.js @@ -18,7 +18,10 @@ function plot(gd, geo, calcData) { var gTraces = Lib.makeTraceGroups(scatterLayer, calcData, 'trace scattergeo'); function removeBADNUM(d, node) { - if(d.lonlat[0] === BADNUM) { + if( + d.lonlat && + d.lonlat[0] === BADNUM + ) { d3.select(node).remove(); } } @@ -98,12 +101,13 @@ function calcGeoJSON(calcTrace, fullLayout) { lonArray = [bboxGeojson[0], bboxGeojson[2]]; latArray = [bboxGeojson[1], bboxGeojson[3]]; } else { - lonArray = new Array(len); - latArray = new Array(len); + lonArray = []; + latArray = []; for(i = 0; i < len; i++) { calcPt = calcTrace[i]; - lonArray[i] = calcPt.lonlat[0]; - latArray[i] = calcPt.lonlat[1]; + if(!calcPt.lonlat) continue; + lonArray.push(calcPt.lonlat[0]); + latArray.push(calcPt.lonlat[1]); } opts.ppad = calcMarkerSize(trace, len); From f958fcaffd0b2121878affd4e155b1aad4b301cb Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Mon, 30 Dec 2024 11:13:06 -0500 Subject: [PATCH 07/11] rename one of the two lonlat variables --- src/traces/scattergeo/hover.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/traces/scattergeo/hover.js b/src/traces/scattergeo/hover.js index b9d54ae6f7d..68fce601e42 100644 --- a/src/traces/scattergeo/hover.js +++ b/src/traces/scattergeo/hover.js @@ -18,12 +18,12 @@ module.exports = function hoverPoints(pointData, xval, yval) { var project = geo.project; function distFn(d) { - var lonlat = d.lonlat; + var _lonlat = d.lonlat; - if(lonlat[0] === BADNUM) return Infinity; - if(isLonLatOverEdges(lonlat)) return Infinity; + if(_lonlat[0] === BADNUM) return Infinity; + if(isLonLatOverEdges(_lonlat)) return Infinity; - var pt = project(lonlat); + var pt = project(_lonlat); var px = project([xval, yval]); var dx = Math.abs(pt[0] - px[0]); var dy = Math.abs(pt[1] - px[1]); From 353a88dd40fe50d3af66bd5ee7cfc179589821e6 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Mon, 30 Dec 2024 11:14:11 -0500 Subject: [PATCH 08/11] check array exists --- src/traces/scattergeo/hover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/scattergeo/hover.js b/src/traces/scattergeo/hover.js index 68fce601e42..eba13e428ae 100644 --- a/src/traces/scattergeo/hover.js +++ b/src/traces/scattergeo/hover.js @@ -20,7 +20,7 @@ module.exports = function hoverPoints(pointData, xval, yval) { function distFn(d) { var _lonlat = d.lonlat; - if(_lonlat[0] === BADNUM) return Infinity; + if(!_lonlat || _lonlat[0] === BADNUM) return Infinity; if(isLonLatOverEdges(_lonlat)) return Infinity; var pt = project(_lonlat); From 00232af5150c195ceea3faddefa58420b39cd768 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Mon, 30 Dec 2024 13:04:24 -0500 Subject: [PATCH 09/11] handle empty latlon --- src/plots/geo/geo.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plots/geo/geo.js b/src/plots/geo/geo.js index 797ab8373b6..09cda8d0a70 100644 --- a/src/plots/geo/geo.js +++ b/src/plots/geo/geo.js @@ -625,6 +625,8 @@ proto._render = function() { var k; function translatePoints(d) { + if(!d.lonlat) return null; + var lonlatPx = projection(d.lonlat); return lonlatPx ? strTranslate(lonlatPx[0], lonlatPx[1]) : @@ -685,6 +687,8 @@ function getProjection(geoLayout) { } projection.isLonLatOverEdges = function(lonlat) { + if(!lonlat) return false; + if(projection(lonlat) === null) { return true; } From e569d43d7d1b2d9397dd461dabfdacc719ce2307 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Mon, 30 Dec 2024 14:13:35 -0500 Subject: [PATCH 10/11] handle empty --- src/components/drawing/index.js | 1 + src/plots/geo/geo.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 5fa39480d6e..4b48a5a1053 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -1775,6 +1775,7 @@ function getMarkerAngle(d, trace) { } if(trace._geo) { + if(!d.latlon) return null; var lon = d.lonlat[0]; var lat = d.lonlat[1]; diff --git a/src/plots/geo/geo.js b/src/plots/geo/geo.js index 09cda8d0a70..32b4321bc94 100644 --- a/src/plots/geo/geo.js +++ b/src/plots/geo/geo.js @@ -553,6 +553,7 @@ proto.makeFramework = function() { // sane lonlat to px _this.project = function(v) { + if(!v) return [null, null]; var px = _this.projection(v); return px ? [px[0] - _this.xaxis._offset, px[1] - _this.yaxis._offset] : From ab5537bd982bb4772f75e2376dc3b1ce0c31dfa5 Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi Date: Thu, 2 Jan 2025 11:54:51 -0500 Subject: [PATCH 11/11] combine regions --- src/lib/geo_location_utils.js | 58 +++++++++++++++++++++++++++++++++-- src/traces/choropleth/plot.js | 5 +-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/lib/geo_location_utils.js b/src/lib/geo_location_utils.js index b45f02b987a..53c49603729 100644 --- a/src/lib/geo_location_utils.js +++ b/src/lib/geo_location_utils.js @@ -41,6 +41,8 @@ function locationToFeature(locationmode, location, features) { var filteredFeatures; var f, i; + var allParts = []; + if(locationId) { if(locationmode === 'USA-states') { // Filter out features out in USA @@ -67,8 +69,7 @@ function locationToFeature(locationmode, location, features) { f.properties && f.properties.iso3cd === locationId ) { - // TODO: we may need to collect all the polygons instead? - return f; + allParts.push(f); } } @@ -78,10 +79,35 @@ function locationToFeature(locationmode, location, features) { ].join(' ')); } + if(allParts.length) { + return allParts; //[allParts.length - 1]; + } + return false; } function feature2polygons(feature) { + if(!Array.isArray(feature)) { + return _feature2polygons(feature); + } + + var polygons; + for(var i = 0; i < feature.length; i++) { + var pts = _feature2polygons(feature[i]); + + if(pts.length) { + if(!polygons) { + polygons = pts; + } else { + polygons.push(polygon.tester(pts)); + } + } + } + + return polygons; +} + +function _feature2polygons(feature) { var geometry = feature.geometry; var coords = geometry.coordinates; var loc = feature.properties.iso3cd || feature.id; @@ -373,9 +399,35 @@ function fetchTraceGeoData(calcData) { return promises; } + +function computeBbox(d) { + if(!Array.isArray(d)) { + return _computeBbox(d); + } + + var minLon = Infinity; + var maxLon = -Infinity; + var minLat = Infinity; + var maxLat = -Infinity; + + for(var i = 0; i < d.length; i++) { + var p = _computeBbox(d[i]); + + minLon = Math.min(minLon, p[0]); + minLat = Math.min(minLat, p[1]); + maxLon = Math.max(maxLon, p[2]); + maxLat = Math.max(maxLat, p[3]); + } + + return [ + minLon, minLat, + maxLon, maxLat + ]; +} + // TODO `turf/bbox` gives wrong result when the input feature/geometry // crosses the anti-meridian. We should try to implement our own bbox logic. -function computeBbox(d) { +function _computeBbox(d) { return turfBbox(d); } diff --git a/src/traces/choropleth/plot.js b/src/traces/choropleth/plot.js index da27bf17d78..6f6be657eb7 100644 --- a/src/traces/choropleth/plot.js +++ b/src/traces/choropleth/plot.js @@ -49,8 +49,9 @@ function calcGeoJSON(calcTrace, fullLayout) { geoUtils.locationToFeature(locationmode, calcPt.loc, features); if(feature) { - calcPt.geojson = feature; - calcPt.ct = feature.properties.ct; + var f0 = Array.isArray(feature) ? feature[0] : feature; + calcPt.geojson = f0; + calcPt.ct = f0.properties.ct; calcPt._polygons = geoUtils.feature2polygons(feature); var bboxFeature = geoUtils.computeBbox(feature);