Skip to content

Commit afa6a60

Browse files
FIREFLY-1105: Support scalar factor in units
1 parent 1f01013 commit afa6a60

File tree

1 file changed

+42
-19
lines changed

1 file changed

+42
-19
lines changed

src/firefly/js/charts/dataTypes/SpectrumUnitConversion.js

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import {WAVELENGTH_UNITS} from 'firefly/visualize/VisUtil';
1212
* @returns {boolean}
1313
*/
1414
export function canUnitConv({from, to}) {
15-
const fromKey = normalizeUnit(from);
16-
const toKey = normalizeUnit(to);
15+
const {unit: fromKey} = normalizeUnit(from);
16+
const {unit: toKey} = normalizeUnit(to);
1717
return !!(fromKey && toKey && UnitXref?.[fromKey]?.[toKey]);
1818
}
1919

@@ -28,9 +28,14 @@ export function canUnitConv({from, to}) {
2828
* @returns {string}
2929
*/
3030
export function getUnitConvExpr({cname, from, to, alias, args=[]}) {
31-
const fromKey = normalizeUnit(from);
32-
const toKey = normalizeUnit(to);
33-
const formula = UnitXref?.[fromKey]?.[toKey] ?? '';
31+
const {unit: fromKey, factor: fromFactor} = normalizeUnit(from);
32+
const {unit: toKey, factor: toFactor} = normalizeUnit(to);
33+
34+
let formula = UnitXref?.[fromKey]?.[toKey] ?? '';
35+
if (fromFactor !== toFactor) {
36+
if (fromFactor) formula = `${formula} * ${fromFactor}`;
37+
if (toFactor) formula = `${formula} / ${toFactor}`; //todo: do we need parantheses here?
38+
}
3439

3540
let colOrExpr = cname;
3641
if (formula) {
@@ -50,14 +55,19 @@ export function getUnitInfo(unit, cname) {
5055
let options = [];
5156
let label = '';
5257

53-
const unitKey = normalizeUnit(unit);
58+
const {unit: unitKey, factor} = normalizeUnit(unit);
5459
if (unitKey) {
5560
const measurementKey = UnitMetadata[unitKey]?.type;
61+
let unitLabel = UnitMetadata[unitKey]?.label || unitKey;
5662
options = Object.entries(UnitMetadata)
5763
.filter(([,meta]) => meta?.type === measurementKey) // can only convert units of the same measurement type
5864
.map(([key, meta]) => ({value: key, label: meta.label || key}));
5965

60-
const unitLabel = UnitMetadata[unitKey]?.label || unitKey;
66+
if (factor) { // add the original unit with its factor as the label and as the first option in conversion dropdown
67+
unitLabel = `${factor} ${unitLabel}`;
68+
options = [{value: unit, label: unitLabel}, ...options];
69+
}
70+
6171
const formattedLabel = Measurement[measurementKey]?.axisLabel;
6272
if (formattedLabel) label = sprintf(formattedLabel, unitLabel);
6373
}
@@ -293,6 +303,7 @@ const UnitMetadata = {
293303
'W/m^2/Hz' : {
294304
type: Measurement.F_NU.key,
295305
label: 'W/m²/Hz',
306+
// aliases are generated at runtime for this and all the flux units below
296307
},
297308
'erg/s/cm^2/Hz' : {
298309
type: Measurement.F_NU.key,
@@ -326,28 +337,42 @@ const UnitMetadata = {
326337
};
327338

328339

340+
// created by using "C.4 The VOUnits grammar" in https://ivoa.net/documents/VOUnits/20231215/REC-VOUnits-1.1.pdf
341+
const VoUnitRegex = /^((?:0\.\d+|[1-9]\d*(?:\.\d+)?)(?:[eE][+-]?[0-9]+)?)(.*)$/;
342+
const splitFactorUnit = (u) => {
343+
const match = u.match(VoUnitRegex);
344+
const factor = match?.[1] ?? '';
345+
const unit = (match?.[2] ?? u).trim(); // fallback to the whole string
346+
return {factor, unit};
347+
};
348+
349+
329350
/**
330-
* Maps any unit representation (value/label/alias) back to a key in UnitXref (and UnitMetadata).
351+
* Maps any unit representation (value/label/alias) back to a key in UnitXref (and UnitMetadata). Also returns the
352+
* scalar factor if present.
331353
* @param u {string} - the unit to normalize
332-
* @return {UnitKey|null} - the key in UnitXref if found, otherwise null
354+
* @return {{unit: UnitKey|null, factor: string}} - the key in UnitXref if found, otherwise null and the scalar factor.
333355
*/
334356
function normalizeUnit(u) {
335-
if (!u) return null;
336-
// u is already a key in UnitXref
337-
if (UnitXref[u]) return u;
357+
if (!u) return {unit: null, factor: ''};
358+
const {factor, unit} = splitFactorUnit(u);
359+
360+
// unit is already a key in UnitXref
361+
if (UnitXref[u]) return {unit, factor};
338362

339363
for (const [key, meta] of Object.entries(UnitMetadata)) {
340-
// u is an alias of a key in UnitXref
364+
// unit is an alias of a key in UnitXref
341365
const aliases = meta?.aliases ?? [];
342366
aliases.push(...getFluxAliases(key));
343-
if (aliases.some((alias) => alias === u)) return key;
367+
if (aliases.some((alias) => alias === unit)) return {unit: key, factor};
344368

345-
// u is a label of a key in UnitXref
346-
if (meta?.label === u) return key;
369+
// unit is a label of a key in UnitXref
370+
if (meta?.label === u) return {unit: key, factor};
347371
}
348-
return null;
372+
return {unit: null, factor};
349373
}
350374

375+
351376
/* Get all possible representations (key, label, aliases) for a given unitKey */
352377
function getAllRepresentations(unitKey, includeLabel=true) {
353378
const meta = UnitMetadata[unitKey];
@@ -408,8 +433,6 @@ const getFluxAliases = (unitKey) => {
408433
.map(([unit, power]) => power === 1 ? unit : `${unit}**${power}`)
409434
.join('.'); // e.g. erg.s**-1.cm**-2.A**-1
410435

411-
// TODO: also support numerical scale-factor in section 2.10 in https://ivoa.net/documents/VOUnits/20231215/REC-VOUnits-1.1.pdf
412-
// possibly alias function can return a boolean to do such matching
413436
aliases.push(fluxUnit, asteriskPowerExpr, invisiblePowerExpr, multiplicationExpr);
414437
}
415438
return Array.from(new Set(aliases)); // remove duplicates

0 commit comments

Comments
 (0)