diff --git a/package-lock.json b/package-lock.json index a754ff15502..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", @@ -113,7 +114,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", @@ -8916,9 +8916,8 @@ }, "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 + "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", diff --git a/package.json b/package.json index c76559d56bd..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", @@ -172,7 +173,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", 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/lib/geo_location_utils.js b/src/lib/geo_location_utils.js index e15659f8f45..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 @@ -63,6 +65,12 @@ 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 + ) { + allParts.push(f); + } } loggers.log([ @@ -71,13 +79,38 @@ 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.id; + var loc = feature.properties.iso3cd || feature.id; var polygons = []; var appendPolygon, j, k, m; @@ -173,6 +206,9 @@ function feature2polygons(feature) { appendPolygon(coords[j]); } break; + case 'LineString': + appendPolygon(coords); + break; } return polygons; @@ -363,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/lib/topojson_utils.js b/src/lib/topojson_utils.js index b533b1a71bf..a0e90a45bac 100644 --- a/src/lib/topojson_utils.js +++ b/src/lib/topojson_utils.js @@ -13,7 +13,16 @@ topojsonUtils.getTopojsonName = function(geoLayout) { }; topojsonUtils.getTopojsonPath = function(topojsonURL, topojsonName) { - return topojsonURL + topojsonName + '.json'; + var path = topojsonURL; + + if(topojsonName.startsWith('un_')) { + path += 'un'; + return 'https://raw.githubusercontent.com/etpinard/sane-topojson/refs/heads/un-borders/dist/un.json'; + } 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/geo.js b/src/plots/geo/geo.js index 797ab8373b6..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] : @@ -625,6 +626,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 +688,8 @@ function getProjection(geoLayout) { } projection.isLonLatOverEdges = function(lonlat) { + if(!lonlat) return false; + if(projection(lonlat) === null) { return true; } 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/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]; 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); diff --git a/src/traces/scattergeo/hover.js b/src/traces/scattergeo/hover.js index b9d54ae6f7d..eba13e428ae 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 || _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]); 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); 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" ]