1
1
/*
2
2
* License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt
3
3
*/
4
- import { get } from 'lodash' ;
5
4
import { sprintf } from '../../externalSource/sprintf.js' ;
5
+ import { WAVELENGTH_UNITS } from 'firefly/visualize/VisUtil' ;
6
6
7
7
/**
8
8
* Return true if 'from' unit can be converted to 'to'.
@@ -12,7 +12,9 @@ import {sprintf} from '../../externalSource/sprintf.js';
12
12
* @returns {boolean }
13
13
*/
14
14
export function canUnitConv ( { from, to} ) {
15
- return ! ! get ( UnitXref , [ from , to ] ) ;
15
+ const fromKey = normalizeUnit ( from ) ;
16
+ const toKey = normalizeUnit ( to ) ;
17
+ return ! ! ( fromKey && toKey && UnitXref ?. [ fromKey ] ?. [ toKey ] ) ;
16
18
}
17
19
18
20
/**
@@ -22,12 +24,14 @@ export function canUnitConv({from, to}) {
22
24
* @param p.from from unit
23
25
* @param p.to to unit
24
26
* @param p.alias the name of this new column
25
- * @param p.colNames a list of all the columns in the table. This is used to format the expression so that column names are quoted correctly.
26
27
* @param p.args any additional arguments used in the conversion formula.
27
28
* @returns {string }
28
29
*/
29
30
export function getUnitConvExpr ( { cname, from, to, alias, args= [ ] } ) {
30
- const formula = get ( UnitXref , [ from , to ] ) ;
31
+ const fromKey = normalizeUnit ( from ) ;
32
+ const toKey = normalizeUnit ( to ) ;
33
+ const formula = UnitXref ?. [ fromKey ] ?. [ toKey ] ?? '' ;
34
+
31
35
let colOrExpr = cname ;
32
36
if ( formula ) {
33
37
colOrExpr = sprintf ( formula . replace ( / ( % ( [ 0 - 9 ] \$ ) ? s ) / g, '"$1"' ) , cname , ...args ) ;
@@ -39,60 +43,62 @@ export function getUnitConvExpr({cname, from, to, alias, args=[]}) {
39
43
/**
40
44
* returns an object containing the axis label and an array of options for unit conversion.
41
45
* @param {string } unit the unit to get the info for
42
- * @param {string } cname the name of the column being evaluated
46
+ * @param {string } cname the name (or expression) of the column being evaluated
43
47
* @returns {Object }
44
48
*/
45
49
export function getUnitInfo ( unit , cname ) {
46
- cname = cname ?. match ( / ^ " ( .+ ) " $ / ) ?. [ 1 ] ?? cname ; // remove enclosing double-quotes if exists
47
- // TODO: account aliases in the lookup
48
- const meas = Object . values ( UnitXref . measurement ) . find ( ( m ) => m ?. options . find ( ( o ) => o ?. value === unit ) ) || { } ;
49
- let label = meas . label ? sprintf ( meas . label , unit ) : '' ;
50
-
51
- // use column name (or expression) if couldn't recognize the unit in options of each measurement
52
- if ( ! label && cname ) {
53
- label = cname + ( unit ? `[${ unit } ]` : '' ) ;
50
+ let options = [ ] ;
51
+ let label = '' ;
52
+
53
+ const unitKey = normalizeUnit ( unit ) ;
54
+ if ( unitKey ) {
55
+ const measurementKey = UnitMetadata [ unitKey ] ?. type ;
56
+ options = Object . entries ( UnitMetadata )
57
+ . filter ( ( [ , meta ] ) => meta ?. type === measurementKey ) // can only convert units of the same measurement type
58
+ . map ( ( [ key , meta ] ) => ( { value : key , label : meta . label || key } ) ) ;
59
+
60
+ const unitLabel = UnitMetadata [ unitKey ] ?. label || unitKey ;
61
+ const formattedLabel = Measurement [ measurementKey ] ?. axisLabel ;
62
+ if ( formattedLabel ) label = sprintf ( formattedLabel , unitLabel ) ;
54
63
}
55
- return { options : meas . options , label} ;
64
+ else {
65
+ cname = cname ?. match ( / ^ " ( .+ ) " $ / ) ?. [ 1 ] ?? cname ; // remove enclosing double-quotes if exists
66
+ if ( cname ) label = cname + ( unit ? ` [${ unit } ]` : '' ) ;
67
+ }
68
+
69
+ return { options, label} ;
56
70
}
57
71
58
72
59
- /*
73
+ /**
74
+ * returns X axis label using the required information like unit, spectral frame options, redshift, etc.
75
+ * @param {string } cname the name of the column being evaluated
76
+ * @param {string } unit the unit to get the info for
77
+ * @param {string } sfLabel the label of spectral frame selected
78
+ * @param {string } redshiftLabel the label of redshift if rest frame, optional
79
+ * @returns {string }
80
+ */
81
+ export const getXLabel = ( cname , unit , sfLabel , redshiftLabel = '' ) => {
82
+ const unitLabel = getUnitInfo ( unit , cname ) . label ;
83
+ return `${ sfLabel } ${ unitLabel } ${ redshiftLabel ? `<br>(${ redshiftLabel } )` : '' } ` ;
84
+ } ;
85
+
86
+
87
+
88
+ /**
89
+ * @typedef {string } UnitKey
90
+ * Unique identifier for a unit, used as a key in UnitXref and UnitMetadata.
91
+ */
92
+
93
+ /**
60
94
Unit conversions, mapping FROM unit -> TO unit, where the formula is an SQL expression.
61
95
The formula is a format string, similar to printf, where the %s is the column name of the value being converted.
62
96
When argument index is needed, it can be referenced as %1$s, %2$s, %3$s, %4$s, etc.
63
- */
64
97
98
+ Note: "outer" layer is the unit you *have*; "inner" layer is the unit you *want*
99
+ @type {Object.<UnitKey, Object.<UnitKey, string>> }
100
+ **/
65
101
const UnitXref = {
66
- measurement : {
67
- frequency : {
68
- options : [ { value :'Hz' } , { value :'KHz' } , { value :'MHz' } , { value :'GHz' } ] ,
69
- label : '𝛎 [%s]'
70
- } ,
71
- wavelength : {
72
- options : [ { value : 'A' } , { value : 'nm' } , { value :'um' } , { value : 'mm' } , { value :'cm' } , { value :'m' } ] ,
73
- label : 'λ [%s]'
74
- } ,
75
- flux_density_frequency : {
76
- options : [ { value :'W/m^2/Hz' } , { value :'erg/s/cm^2/Hz' } , { value :'Jy' } ] ,
77
- label : 'F𝛎 [%s]'
78
- } ,
79
- flux_density_wavelength : {
80
- options : [ { value :'W/m^2/um' } , { value :'erg/s/cm^2/A' } ] ,
81
- label : 'Fλ [%s]'
82
- } ,
83
- inband_flux : {
84
- options : [ { value :'W/m^2' } , { value :'Jy*Hz' } ] ,
85
- label : '𝛎*F𝛎 [%s]'
86
- }
87
- } ,
88
-
89
- aliases : {
90
- //TODO: add Angstrom, dot product notations of flux density, etc.
91
- } ,
92
-
93
- // Unit Conversions follow
94
- // "outer" layer is the unit you *have*; "inner" layer is the unit you *want*
95
-
96
102
// frequency -------------
97
103
Hz : {
98
104
Hz : '%s' ,
@@ -210,15 +216,134 @@ const UnitXref = {
210
216
} ,
211
217
} ;
212
218
219
+
213
220
/**
214
- * returns X axis label using the required information like unit, spectral frame options, redshift, etc.
215
- * @param {string } cname the name of the column being evaluated
216
- * @param {string } unit the unit to get the info for
217
- * @param {string } sfLabel the label of spectral frame selected
218
- * @param {string } redshiftLabel the label of redshift if rest frame, optional
219
- * @returns {string }
221
+ * @typedef {string } MeasurementKey - unique identifier for a measurement type, used as a key in Measurement.
220
222
*/
221
- export const getXLabel = ( cname , unit , sfLabel , redshiftLabel = '' ) => {
222
- const unitLabel = getUnitInfo ( unit , cname ) . label ;
223
- return `${ sfLabel } ${ unitLabel } ${ redshiftLabel ? `<br>(${ redshiftLabel } )` : '' } ` ;
224
- } ;
223
+
224
+ /**
225
+ * @typedef {Object } Measurement
226
+ * @property {MeasurementKey } key - the key of the measurement type
227
+ * @property {string } value - the value of the measurement type
228
+ * @property {string } axisLabel - the label for the axis, formatted with a placeholder for the unit
229
+ */
230
+
231
+ /**Type of measurements by which units are grouped.
232
+ * @type {Object.<MeasurementKey, Measurement> }
233
+ */
234
+ const Measurement = {
235
+ NU : { key : 'NU' , value : 'frequency' , axisLabel : '𝛎 [%s]' } ,
236
+ LAMBDA : { key : 'LAMBDA' , value : 'wavelength' , axisLabel : 'λ [%s]' } ,
237
+ F_NU : { key : 'F_NU' , value : 'flux_density_frequency' , axisLabel : 'F𝛎 [%s]' } ,
238
+ F_LAMBDA : { key : 'F_LAMBDA' , value : 'flux_density_wavelength' , axisLabel : 'Fλ [%s]' } ,
239
+ F : { key : 'F' , value : 'inband_flux' , axisLabel : '𝛎·F𝛎 [%s]' } ,
240
+ } ;
241
+
242
+
243
+ /**
244
+ * @typedef {Object } UnitMetadata
245
+ * @property {MeasurementKey } type - the type of measurement this unit belongs to, one of the keys in `Measurement`.
246
+ * @property {string } label - the label for the unit, used in dropdown options and axis labels. If undefined, the key
247
+ * is used as the label.
248
+ * @property {Array<string> } aliases - an object containing aliases for the unit, used for case-insensitive matching. If
249
+ * undefined, only the key (and label) is used for matching.
250
+ */
251
+
252
+ /**
253
+ * Metadata of each unit, including its type, label, and aliases. Keys are the same as in UnitXref.
254
+ * @type {Object.<UnitKey, UnitMetadata> }
255
+ * **/
256
+ const UnitMetadata = {
257
+ // frequency -------------
258
+ Hz : {
259
+ type : Measurement . NU . key ,
260
+ } ,
261
+ KHz : {
262
+ type : Measurement . NU . key ,
263
+ } ,
264
+ MHz : {
265
+ type : Measurement . NU . key ,
266
+ } ,
267
+ GHz : {
268
+ type : Measurement . NU . key ,
269
+ } ,
270
+ // wavelength -------------
271
+ A : {
272
+ type : Measurement . LAMBDA . key ,
273
+ label : WAVELENGTH_UNITS . angstrom . symbol ,
274
+ aliases : [ 'angstrom' , 'angstroms' ] //case-insensitive
275
+ } ,
276
+ nm : {
277
+ type : Measurement . LAMBDA . key ,
278
+ } ,
279
+ um : {
280
+ type : Measurement . LAMBDA . key ,
281
+ label : WAVELENGTH_UNITS . um . symbol ,
282
+ aliases : [ 'micron' , 'microns' ] //case-insensitive
283
+ } ,
284
+ mm : {
285
+ type : Measurement . LAMBDA . key ,
286
+ } ,
287
+ cm : {
288
+ type : Measurement . LAMBDA . key ,
289
+ } ,
290
+ m : {
291
+ type : Measurement . LAMBDA . key ,
292
+ } ,
293
+ // flux density in frequency space -------------
294
+ 'W/m^2/Hz' : {
295
+ type : Measurement . F_NU . key ,
296
+ label : 'W/m²/Hz' ,
297
+ aliases : [ ] , //TODO: generate aliases in dot product notation with **, as it is with ** for powers
298
+ } ,
299
+ 'erg/s/cm^2/Hz' : {
300
+ type : Measurement . F_NU . key ,
301
+ label : 'erg/s/cm²/Hz' ,
302
+ } ,
303
+ Jy : {
304
+ type : Measurement . F_NU . key ,
305
+ } ,
306
+ // flux density in wavelength space -------------
307
+ 'erg/s/cm^2/A' : {
308
+ type : Measurement . F_LAMBDA . key ,
309
+ label : `erg/s/cm²/${ WAVELENGTH_UNITS . angstrom . symbol } ` ,
310
+ } ,
311
+ 'W/m^2/um' : {
312
+ type : Measurement . F_LAMBDA . key ,
313
+ label : `W/m²/${ WAVELENGTH_UNITS . um . symbol } ` ,
314
+ } ,
315
+ // inband flux (independent of frequency or wavelength) -------------
316
+ 'W/m^2' : {
317
+ type : Measurement . F . key ,
318
+ label : 'W/m²' ,
319
+ } ,
320
+ 'erg/s/cm^2' : {
321
+ type : Measurement . F . key ,
322
+ label : 'erg/s/cm²' ,
323
+ } ,
324
+ 'Jy*Hz' : {
325
+ type : Measurement . F . key ,
326
+ label : 'Jy·Hz' ,
327
+ } ,
328
+ } ;
329
+
330
+
331
+ /**
332
+ * Maps any unit value/label/alias back to a key in UnitXref (and UnitMetadata).
333
+ * @param u {string} - the unit to normalize
334
+ * @return {UnitKey|null } - the key in UnitXref if found, otherwise null
335
+ */
336
+ function normalizeUnit ( u ) {
337
+ if ( ! u ) return null ;
338
+ // u is already a key in UnitXref
339
+ if ( UnitXref [ u ] ) return u ;
340
+
341
+ for ( const [ key , meta ] of Object . entries ( UnitMetadata ) ) {
342
+ // u is a case-insensitive alias of a key in UnitXref
343
+ if ( meta ?. aliases ?. some ( ( alias ) => alias . toLowerCase ( ) === u . toLowerCase ( ) ) ) return key ;
344
+
345
+ // u is a label of a key in UnitXref
346
+ if ( meta ?. label === u ) return key ;
347
+ }
348
+ return null ;
349
+ }
0 commit comments