@@ -20,6 +20,10 @@ import {
2020import { constant , isObject } from "./options.js" ;
2121import { warn } from "./warnings.js" ;
2222
23+ const pi = Math . PI ;
24+ const tau = 2 * pi ;
25+ const golden = 1.618 ;
26+
2327export function Projection (
2428 {
2529 projection,
@@ -76,6 +80,7 @@ export function Projection(
7680 let transform ;
7781
7882 // If a domain is specified, fit the projection to the frame.
83+ let ratio = projection . ratio ;
7984 if ( domain != null ) {
8085 const [ [ x0 , y0 ] , [ x1 , y1 ] ] = geoPath ( projection ) . bounds ( domain ) ;
8186 const k = Math . min ( dx / ( x1 - x0 ) , dy / ( y1 - y0 ) ) ;
@@ -87,6 +92,7 @@ export function Projection(
8792 this . stream . point ( x * k + tx , y * k + ty ) ;
8893 }
8994 } ) ;
95+ ratio = Math . max ( 0.5 , Math . min ( golden , ( y1 - y0 ) / ( x1 - x0 ) ) ) ;
9096 } else {
9197 warn ( `Warning: the projection could not be fit to the specified domain; using the default scale.` ) ;
9298 }
@@ -101,20 +107,21 @@ export function Projection(
101107 }
102108 } ) ;
103109
104- return { stream : ( s ) => projection . stream ( transform . stream ( clip ( s ) ) ) } ;
110+ return { stream : ( s ) => projection . stream ( transform . stream ( clip ( s ) ) ) , ratio } ;
105111}
106112
107- export function hasProjection ( { projection} = { } ) {
108- if ( projection == null ) return false ;
109- if ( typeof projection . stream === "function" ) return true ; // d3 projection
110- if ( isObject ( projection ) ) ( { type : projection } = projection ) ;
111- if ( typeof projection !== "function" ) projection = namedProjection ( projection ) ;
112- return projection != null ;
113+ // When a projection is specified, try to determine a good value for the
114+ // projection’s height, if it is a named projection. When we don’t have a way to
115+ // know, the golden ratio is our best guess.
116+ export function projectionAspectRatio ( { projection} = { } , geometry ) {
117+ projection = Projection (
118+ { projection} ,
119+ { width : 100 , height : 300 , marginLeft : 0 , marginRight : 0 , marginTop : 0 , marginBottom : 0 }
120+ ) ;
121+ if ( projection == null ) return geometry ? golden - 1 : 0 ;
122+ return projection . ratio ?? golden - 1 ;
113123}
114124
115- const pi = Math . PI ;
116- const tau = 2 * pi ;
117-
118125function namedProjection ( projection ) {
119126 switch ( `${ projection } ` . toLowerCase ( ) ) {
120127 case "albers-usa" :
@@ -142,7 +149,7 @@ function namedProjection(projection) {
142149 case "reflect-y" :
143150 return reflectY ;
144151 case "mercator" :
145- return scaleProjection ( geoMercator , tau , tau ) ;
152+ return scaleProjection ( geoMercator , tau , tau , 1 ) ;
146153 case "orthographic" :
147154 return scaleProjection ( geoOrthographic , 2 , 2 ) ;
148155 case "stereographic" :
@@ -165,14 +172,15 @@ function maybePostClip(clip, x1, y1, x2, y2) {
165172 }
166173}
167174
168- function scaleProjection ( createProjection , kx , ky ) {
175+ function scaleProjection ( createProjection , kx , ky , ratio ) {
169176 return ( { width, height, rotate, precision = 0.15 , clip} ) => {
170177 const projection = createProjection ( ) ;
171178 if ( precision != null ) projection . precision ?. ( precision ) ;
172179 if ( rotate != null ) projection . rotate ?. ( rotate ) ;
173180 if ( typeof clip === "number" ) projection . clipAngle ?. ( clip ) ;
174181 projection . scale ( Math . min ( width / kx , height / ky ) ) ;
175182 projection . translate ( [ width / 2 , height / 2 ] ) ;
183+ if ( ratio > 0 ) projection . ratio = ratio ;
176184 return projection ;
177185 } ;
178186}
0 commit comments