@@ -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,54 +107,43 @@ export function Projection(
101107 }
102108 } ) ;
103109
104- return { stream : ( s ) => projection . stream ( transform . stream ( clip ( s ) ) ) } ;
105- }
106-
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 ;
110+ return { stream : ( s ) => projection . stream ( transform . stream ( clip ( s ) ) ) , ratio} ;
113111}
114112
115- const pi = Math . PI ;
116- const tau = 2 * pi ;
117-
118113function namedProjection ( projection ) {
119114 switch ( `${ projection } ` . toLowerCase ( ) ) {
120115 case "albers-usa" :
121- return scaleProjection ( geoAlbersUsa , 0.7463 , 0.4673 ) ;
116+ return scaleProjection ( geoAlbersUsa , 0.7463 , 0.4673 , 610 / 975 ) ;
122117 case "albers" :
123- return conicProjection ( geoAlbers , 0.7463 , 0.4673 ) ;
118+ return conicProjection ( geoAlbers , 0.7463 , 0.4673 , 610 / 975 ) ;
124119 case "azimuthal-equal-area" :
125- return scaleProjection ( geoAzimuthalEqualArea , 4 , 4 ) ;
120+ return scaleProjection ( geoAzimuthalEqualArea , 4 , 4 , 1 ) ;
126121 case "azimuthal-equidistant" :
127- return scaleProjection ( geoAzimuthalEquidistant , tau , tau ) ;
122+ return scaleProjection ( geoAzimuthalEquidistant , tau , tau , 1 ) ;
128123 case "conic-conformal" :
129124 return conicProjection ( geoConicConformal , tau , tau ) ;
130125 case "conic-equal-area" :
131126 return conicProjection ( geoConicEqualArea , 6.1702 , 2.9781 ) ;
132127 case "conic-equidistant" :
133128 return conicProjection ( geoConicEquidistant , 7.312 , 3.6282 ) ;
134129 case "equal-earth" :
135- return scaleProjection ( geoEqualEarth , 5.4133 , 2.6347 ) ;
130+ return scaleProjection ( geoEqualEarth , 5.4133 , 2.6347 , 0.4867 ) ;
136131 case "equirectangular" :
137- return scaleProjection ( geoEquirectangular , tau , pi ) ;
132+ return scaleProjection ( geoEquirectangular , tau , pi , 0.5 ) ;
138133 case "gnomonic" :
139- return scaleProjection ( geoGnomonic , 3.4641 , 3.4641 ) ;
134+ return scaleProjection ( geoGnomonic , 3.4641 , 3.4641 , 1 ) ;
140135 case "identity" :
141136 return identity ;
142137 case "reflect-y" :
143138 return reflectY ;
144139 case "mercator" :
145- return scaleProjection ( geoMercator , tau , tau ) ;
140+ return scaleProjection ( geoMercator , tau , tau , 1 ) ;
146141 case "orthographic" :
147- return scaleProjection ( geoOrthographic , 2 , 2 ) ;
142+ return scaleProjection ( geoOrthographic , 2 , 2 , 1 ) ;
148143 case "stereographic" :
149- return scaleProjection ( geoStereographic , 2 , 2 ) ;
144+ return scaleProjection ( geoStereographic , 2 , 2 , 1 ) ;
150145 case "transverse-mercator" :
151- return scaleProjection ( geoTransverseMercator , tau , tau ) ;
146+ return scaleProjection ( geoTransverseMercator , tau , tau , 1 ) ;
152147 default :
153148 throw new Error ( `unknown projection type: ${ projection } ` ) ;
154149 }
@@ -165,14 +160,15 @@ function maybePostClip(clip, x1, y1, x2, y2) {
165160 }
166161}
167162
168- function scaleProjection ( createProjection , kx , ky ) {
163+ function scaleProjection ( createProjection , kx , ky , ratio ) {
169164 return ( { width, height, rotate, precision = 0.15 , clip} ) => {
170165 const projection = createProjection ( ) ;
171166 if ( precision != null ) projection . precision ?. ( precision ) ;
172167 if ( rotate != null ) projection . rotate ?. ( rotate ) ;
173168 if ( typeof clip === "number" ) projection . clipAngle ?. ( clip ) ;
174169 projection . scale ( Math . min ( width / kx , height / ky ) ) ;
175170 projection . translate ( [ width / 2 , height / 2 ] ) ;
171+ if ( ratio > 0 ) projection . ratio = ratio ;
176172 return projection ;
177173 } ;
178174}
@@ -218,3 +214,15 @@ export function applyProjection(values, projection) {
218214 stream . point ( x [ i ] , y [ i ] ) ;
219215 }
220216}
217+
218+ // When a projection is specified, try to determine a good value for the
219+ // projection’s height, if it is a named projection. When we don’t have a way to
220+ // know, the golden ratio is our best guess.
221+ export function projectionFitRatio ( { projection} = { } , geometry ) {
222+ projection = Projection (
223+ { projection} ,
224+ { width : 100 , height : 300 , marginLeft : 0 , marginRight : 0 , marginTop : 0 , marginBottom : 0 }
225+ ) ;
226+ if ( projection == null ) return geometry ? golden - 1 : 0 ;
227+ return projection . ratio ?? golden - 1 ;
228+ }
0 commit comments