@@ -16,7 +16,7 @@ var Drawing = require('../../components/drawing');
1616var Fx = require ( '../../components/fx' ) ;
1717var Plots = require ( '../plots' ) ;
1818var Axes = require ( '../cartesian/axes' ) ;
19- var getAutoRange = require ( '../cartesian/autorange' ) . getAutoRange ;
19+ const { concatExtremes , getAutoRange, makePadFn } = require ( '../cartesian/autorange' ) ;
2020var dragElement = require ( '../../components/dragelement' ) ;
2121var prepSelect = require ( '../../components/selections' ) . prepSelect ;
2222var clearOutline = require ( '../../components/selections' ) . clearOutline ;
@@ -26,7 +26,7 @@ var createGeoZoom = require('./zoom');
2626var constants = require ( './constants' ) ;
2727
2828var geoUtils = require ( '../../lib/geo_location_utils' ) ;
29- const { getFitboundsLonRange , unwrapLonRange } = geoUtils ;
29+ const { unwrapLonRange } = geoUtils ;
3030var topojsonUtils = require ( '../../lib/topojson_utils' ) ;
3131var topojsonFeature = require ( 'topojson-client' ) . feature ;
3232
@@ -237,51 +237,40 @@ proto.updateProjection = function (geoCalcData, fullLayout) {
237237 axLon . range = getAutoRange ( gd , axLon ) ;
238238 axLat . range = getAutoRange ( gd , axLat ) ;
239239
240- // For point data straddling the antimeridian (±180°), the naive [min, max]
241- // longitude range above can include a large empty span; prefer the compact
242- // crossing range instead. Restricted to fitbounds='locations' with no
243- // region-bearing traces: choropleth, scattergeo `locations`, and the
244- // geojson-bbox path used by fitbounds='geojson' + locationmode='geojson-id'
245- // all carry region extents that per-point lonlat centroids don't capture.
246- if ( ! this . hasChoropleth && geoLayout . fitbounds === 'locations' ) {
247- var lons = [ ] ;
248- var hasLocationData = false ;
249-
250- for ( var i = 0 ; i < geoCalcData . length ; i ++ ) {
251- var calcTrace = geoCalcData [ i ] ;
252- var fitTrace = calcTrace [ 0 ] . trace ;
253-
254- // only visible traces contribute to the autorange above
255- if ( fitTrace . visible !== true ) continue ;
256- if ( fitTrace . locations ?. length ) {
257- hasLocationData = true ;
258- break ;
259- }
260- for ( var j = 0 ; j < calcTrace . length ; j ++ ) {
261- var lonlat = calcTrace [ j ] . lonlat ;
262- if ( lonlat ) lons . push ( lonlat [ 0 ] ) ;
263- }
264- }
240+ // Min/maxing the per-trace ranges above breaks when data crosses the
241+ // antimeridian, since `computeBbox` unwraps an east edge past 180°. Bounding
242+ // every coordinate at once lets `geoBounds` pick the compact range instead.
243+ const fitCoordParts = [ ] ;
265244
266- if ( ! hasLocationData ) {
267- var fitLonRange = getFitboundsLonRange ( lons ) ;
268- if ( fitLonRange ) {
269- // getFitboundsLonRange returns a tight [min, max]. getAutoRange
270- // pads the naive range (for marker size and the standard
271- // margin), so scale that padding to the narrower crossing range
272- // and apply it, keeping markers off the frame edge as on any
273- // other fitbounds map. The padding is symmetric, so the
274- // mid-longitude the projection centers on is unchanged.
275- var lonDataSpan = Lib . aggNums ( Math . max , null , lons ) - Lib . aggNums ( Math . min , null , lons ) ;
276- var lonPad =
277- lonDataSpan > 0
278- ? ( ( ( axLon . range [ 1 ] - axLon . range [ 0 ] - lonDataSpan ) / 2 ) *
279- ( fitLonRange [ 1 ] - fitLonRange [ 0 ] ) ) /
280- lonDataSpan
281- : 0 ;
282- axLon . range = [ fitLonRange [ 0 ] - lonPad , fitLonRange [ 1 ] + lonPad ] ;
283- }
284- }
245+ for ( const calcTrace of geoCalcData ) {
246+ const fitTrace = calcTrace [ 0 ] . trace ;
247+ if ( fitTrace . visible !== true ) continue ;
248+
249+ if ( fitTrace . _module . fitCoords ) fitCoordParts . push ( fitTrace . _module . fitCoords ( calcTrace , geoLayout ) ) ;
250+ }
251+
252+ // Get the extents in the same manner as getAutoRange
253+ const lonExtremes = concatExtremes ( gd , axLon ) ;
254+ const lonDataMin = lonExtremes . min . reduce ( ( min , { val } ) => Math . min ( min , val ) , Infinity ) ;
255+ const lonDataMax = lonExtremes . max . reduce ( ( max , { val } ) => Math . max ( max , val ) , - Infinity ) ;
256+ const lonDataSpan = lonDataMax - lonDataMin ;
257+ const fitBbox = geoUtils . boundsOfCoords ( fitCoordParts . flat ( ) ) ;
258+ const [ fitWest , , fitEast ] = fitBbox || [ ] ;
259+ const fitSpan = fitEast - fitWest ;
260+ const useFit = Boolean ( fitBbox ) && ( lonDataSpan > 360 || ( lonDataSpan < 360 && fitSpan < lonDataSpan ) ) ;
261+
262+ // Add padding in the same manner as getAutoRange. Ideally this could use an
263+ // underlying helper function, but that doesn't exist yet so we handle it like this.
264+ if ( useFit ) {
265+ const getPadMin = makePadFn ( fullLayout , axLon , 0 ) ;
266+ const getPadMax = makePadFn ( fullLayout , axLon , 1 ) ;
267+ const padMin = lonExtremes . min . reduce ( ( max , pt ) => Math . max ( max , getPadMin ( pt ) ) , 0 ) ;
268+ const padMax = lonExtremes . max . reduce ( ( max , pt ) => Math . max ( max , getPadMax ( pt ) ) , 0 ) ;
269+ const usable = axLon . _length - padMin - padMax ;
270+ const paddedSpan = usable > axLon . _length / 10 ? ( fitSpan * axLon . _length ) / usable : fitSpan ;
271+ const fitMid = ( fitWest + fitEast ) / 2 ;
272+
273+ axLon . range = [ fitMid - paddedSpan / 2 , fitMid + paddedSpan / 2 ] ;
285274 }
286275
287276 var midLon = ( axLon . range [ 0 ] + axLon . range [ 1 ] ) / 2 ;
0 commit comments