From 1986a06cde4772e1815d8cf63a8ebed25d5eb292 Mon Sep 17 00:00:00 2001 From: Luc Claustres Date: Mon, 9 Sep 2024 16:30:50 +0200 Subject: [PATCH] refactor: unit system functions now returning/taking definition objects and unit conversions in time series/data table --- core/client/components/chart/KDataTable.vue | 8 +-- .../components/chart/KTimeSeriesChart.vue | 15 +---- core/client/units.js | 60 ++++++++++++------- core/client/utils/index.js | 1 - core/client/utils/utils.data.js | 22 ------- map/client/utils/utils.time-series.js | 8 ++- 6 files changed, 48 insertions(+), 66 deletions(-) delete mode 100644 core/client/utils/utils.data.js diff --git a/core/client/components/chart/KDataTable.vue b/core/client/components/chart/KDataTable.vue index c4ce664e8..4af32d39e 100644 --- a/core/client/components/chart/KDataTable.vue +++ b/core/client/components/chart/KDataTable.vue @@ -30,7 +30,7 @@ import _ from 'lodash' import moment from 'moment' import Papa from 'papaparse' import { ref, watch } from 'vue' -import { downloadAsBlob, convertTimeSerie } from '../../utils' +import { downloadAsBlob } from '../../utils' import { useSchema } from '../../composables' import { Units } from '../../units.js' import { Time } from '../../time.js' @@ -71,7 +71,6 @@ const height = ref(0) // Used to store template compilers per field const compilers = {} const exportCompilers = {} -let propertiesToConvert = [] // Watch watch(() => props.tables, update) @@ -82,14 +81,11 @@ async function update () { await compile(props.schema) columns.value = [] const invisibleColumns = [] - propertiesToConvert = [] _.forOwn(schema.value.properties, (value, key) => { const type = _.get(value, 'type') // FIXME: allow for custom representation of complex objects if (type === 'object') return const label = _.get(value, 'field.label', _.get(value, 'field.helper', key)) - const convertToDefaultUnit = _.get(value, 'field.defaultUnit', false) - if (convertToDefaultUnit) propertiesToConvert.push(key) const visible = _.get(value, 'field.visible', true) if (!visible) invisibleColumns.push(key) const formatter = _.has(value, 'field.formatter') ? _.get(props.formatters, value.field.formatter) : null @@ -135,7 +131,6 @@ async function update () { rows.value = [] for (const table of props.tables) { const data = await table.data - convertTimeSerie(data, table.variable, propertiesToConvert) rows.value = rows.value.concat(data) } } @@ -148,7 +143,6 @@ async function exportData (options = {}) { for (let i = 0; i < props.tables.length; i++) { const table = props.tables[i] const data = await table.data - await convertTimeSerie(data, table.variable, propertiesToConvert) for (const item of data) { const row = {} _.forOwn(schema.value.properties, (value, key) => { diff --git a/core/client/components/chart/KTimeSeriesChart.vue b/core/client/components/chart/KTimeSeriesChart.vue index f4c2dce1e..fc6f6ac13 100644 --- a/core/client/components/chart/KTimeSeriesChart.vue +++ b/core/client/components/chart/KTimeSeriesChart.vue @@ -81,9 +81,6 @@ function getUnit (timeSerie) { function getTargetUnit (timeSerie) { return _.get(timeSerie, 'variable.targetUnit') } -function setUnit (timeserie, targetUnit) { - _.set(timeserie, 'variable.unit', targetUnit) -} function getZoom () { const start = moment.utc(_.get(chart, 'scales.x.min')) const end = moment.utc(_.get(chart, 'scales.x.max')) @@ -246,8 +243,8 @@ function makeScales (datasets) { let axisId = 0 for (const timeSerie of props.timeSeries) { const unit = getUnit(timeSerie) - const targetUnit = getTargetUnit(timeSerie) || unit - const unitName = targetUnit.name + const targetUnit = getTargetUnit(timeSerie) + const unitName = (targetUnit ? targetUnit.name : unit.name) if (!unit2axis.has(unitName)) { // Ensure a related dataset does exist const axisDatasets = _.filter(datasets, dataset => (_.get(dataset, 'targetUnit.name', _.get(dataset, 'unit.name')) === unitName)) @@ -257,12 +254,11 @@ function makeScales (datasets) { axisDatasets.forEach(dataset => Object.assign(dataset, { yAxisID: axis })) unit2axis.set(unitName, axis) scales[axis] = _.merge({ - targetUnit: unitName, type: props.logarithmic ? 'logarithmic' : 'linear', position: (axisId + 1) % 2 ? 'left' : 'right', title: { display: true, - text: i18n.tie(targetUnit.symbol) + text: i18n.tie(targetUnit ? targetUnit.symbol : unit.symbol) }, ticks: { callback: function (value, index, values) { @@ -288,7 +284,6 @@ async function makeDatasets () { const label = _.get(timeSerie, 'variable.label') const unit = getUnit(timeSerie) const targetUnit = getTargetUnit(timeSerie) - if (targetUnit) setUnit(timeSerie, targetUnit) const data = await timeSerie.data // No data ? if (_.isEmpty(data)) continue @@ -308,10 +303,6 @@ async function makeDatasets () { if (_.has(item, yAxisKey)) { let value = _.get(item, yAxisKey) if (_.isFinite(value)) { - if (targetUnit) { - value = Units.convert(value, unit.name, targetUnit.name) - _.set(item, yAxisKey, value) - } if (_.isNil(min[unitName]) || (value < min[unitName])) min[unitName] = value if (_.isNil(max[unitName]) || (value > max[unitName])) max[unitName] = value } diff --git a/core/client/units.js b/core/client/units.js index b5c445f4c..85b2636a8 100644 --- a/core/client/units.js +++ b/core/client/units.js @@ -286,15 +286,25 @@ export const Units = { }, // Get unit definition by name getUnit (unit) { - return _.find(this.getUnits(), { name: unit }) + // Not optimized + //return _.find(this.getUnits(), { name: unit }) + let definition + _.forOwn(this.get(), (units, quantity) => { + // Already found ? + if (definition) return + else if (_.has(units, unit)) definition = Object.assign({ name: unit, quantity }, _.get(units, unit)) + }) + return definition }, - // Get unit symbol by unit name + // Get unit symbol by unit name/definition getUnitSymbol (unit) { - const definition = this.getUnit(unit) + const definition = (typeof unit === 'object' ? unit : this.getUnit(unit)) return (definition ? i18n.tie(definition.symbol) : unit) }, - // Get default unit definition (if any) for a given quantity/unit name + // Get default unit definition (if any) for a given quantity/unit name/definition getDefaultUnit (quantityOrUnit) { + if (!quantityOrUnit) return null + if (typeof quantityOrUnit === 'object') quantityOrUnit = quantityOrUnit.name // Check for quantity first let defaultUnit = Store.get(`units.default.${quantityOrUnit}`) // If not check by matching quantity based on given unit @@ -302,44 +312,50 @@ export const Units = { const baseUnit = this.getUnit(quantityOrUnit) // Get default unit for this quantity instead if (baseUnit) defaultUnit = this.getDefaultUnit(baseUnit.quantity) + } else { + // Jump from name to definition + defaultUnit = this.getUnit(defaultUnit) } return defaultUnit }, + // Set default unit name for a quantity setDefaultUnit (quantity, unit) { Store.set(`units.default.${quantity}`, unit) }, - // Get symbol of default unit (if any) for a given quantity/unit name + // Get symbol of default unit (if any) for a given quantity/unit name/definition getDefaultUnitSymbol (quantityOrUnit) { return this.getUnitSymbol(this.getDefaultUnit(quantityOrUnit)) }, - // Get target unit for a source unit, will be default unit (if any) or source unit + // Get target unit definition for a source unit name/definition, will be default unit (if any) or source unit getTargetUnit (sourceUnit) { - return this.getDefaultUnit(sourceUnit) || sourceUnit + return this.getDefaultUnit(sourceUnit) || (typeof sourceUnit === 'object' ? sourceUnit : this.getUnit(sourceUnit)) }, - // Get target unit symbol for a source unit, will be default unit symbol (if any) or source unit symbol + // Get target unit symbol for a source unit name/definition, will be default unit symbol (if any) or source unit symbol getTargetUnitSymbol (sourceUnit) { return this.getUnitSymbol(this.getTargetUnit(sourceUnit)) }, - // Convert between units by names + // Convert between units by names/definitions // If target unit is not specified will use default unit (if any) for source unit convert (value, sourceUnit, targetUnit) { - if (_.isNil(value)) { - logger.warn('[KDK] cannont convert a nil value') - return - } + if (_.isNil(value) || !_.isFinite(value)) return value if (value === Number.MIN_VALUE || value === Number.MAX_VALUE) return value - // If target unit is same as source unit does nothing - if (targetUnit === sourceUnit) return value + if (typeof sourceUnit === 'string') sourceUnit = this.getUnit(sourceUnit) + if (typeof targetUnit === 'string') targetUnit = this.getUnit(targetUnit) // If target unit is not given use default one if (!targetUnit) targetUnit = this.getDefaultUnit(sourceUnit) - // Check if the target unit does exist - if (!targetUnit) return value - // Check if the source unit does exist - if (!math.Unit.isValuelessUnit(sourceUnit)) return value - let n = math.unit(value, sourceUnit) - n = n.toNumber(targetUnit) + // Check if the source/target unit does exist + if (!sourceUnit || !targetUnit) return value + // If target unit is same as source unit does nothing + if (sourceUnit && targetUnit && (targetUnit.name === sourceUnit.name)) return value + // Check if the source unit is declared in the units system + if (!math.Unit.isValuelessUnit(sourceUnit.name)) return value + // Check if the value is a valid number + if (!_.isFinite(value)) return value + // Now convert + let n = math.unit(value, sourceUnit.name) + n = n.toNumber(targetUnit.name) // Remap from [-180,+180[ to [0,360[ for angles - n = (targetUnit === 'deg' ? (n < 0.0 ? n + 360.0 : n) : n) + n = (targetUnit.name === 'deg' ? (n < 0.0 ? n + 360.0 : n) : n) return n }, // Format display of source value in target unit, converting from source unit diff --git a/core/client/utils/index.js b/core/client/utils/index.js index ddaabc623..bd5429413 100644 --- a/core/client/utils/index.js +++ b/core/client/utils/index.js @@ -8,7 +8,6 @@ export * from './utils.account.js' export * from './utils.actions.js' export * from './utils.colors.js' export * from './utils.content.js' -export * from './utils.data.js' export * from './utils.locale.js' export * from './utils.math.js' export * from './utils.platform.js' diff --git a/core/client/utils/utils.data.js b/core/client/utils/utils.data.js deleted file mode 100644 index 31b402a26..000000000 --- a/core/client/utils/utils.data.js +++ /dev/null @@ -1,22 +0,0 @@ -import _ from 'lodash' -import { Units } from '../units.js' - -export function convertData (data, valuePaths, sourceUnit, targetUnit) { - if (!Array.isArray(valuePaths)) valuePaths = [valuePaths] - _.forEach(data, document => { - _.forEach(valuePaths, valuePath => { - const value = _.get(document, valuePath) - if (value) _.set(document, valuePath, Units.convert(value, sourceUnit.name, targetUnit.name)) - }) - }) -} - -export function convertTimeSerie (data, variable, valuePaths) { - if (!Array.isArray(valuePaths)) valuePaths = [valuePaths] - const unit = variable.unit - const targetUnit = variable.targetUnit || Units.getDefaultUnit(unit.name) - if (unit.name !== targetUnit.name) { - convertData(data, valuePaths, unit, targetUnit) - variable.unit = targetUnit - } -} \ No newline at end of file diff --git a/map/client/utils/utils.time-series.js b/map/client/utils/utils.time-series.js index 53ac1084a..505c99b38 100644 --- a/map/client/utils/utils.time-series.js +++ b/map/client/utils/utils.time-series.js @@ -16,7 +16,10 @@ async function getDataForVariable(data, variable, forecastLevel, runTime) { // Aggregated variable available for feature ? if (properties[name] && Array.isArray(properties[name])) { // Build data structure as expected by visualisation - values = properties[name].map((value, index) => ({ time: moment.utc(times[name][index]).valueOf(), [name]: value })) + values = properties[name].map((value, index) => { + value = Units.convert(value, variable.unit, variable.targetUnit) + return { time: moment.utc(times[name][index]).valueOf(), [name]: value } + }) // Keep only selected value if multiple are provided for the same time (eg different forecasts) if (variable.runTimes && runTime && !_.isEmpty(_.get(runTimes, name))) { values = values.filter((value, index) => (runTimes[name][index] === runTime.toISOString())) @@ -86,6 +89,7 @@ export function getTimeSeries({ const baseUnit = _.get(properties, 'unit', variable.unit) // Known by the unit system ? const unit = Units.getUnit(baseUnit) || { name: baseUnit } + const targetUnit = Units.getTargetUnit(baseUnit) const serie = { probedLocation: data, data: getDataForVariable(data, variable, forecastLevel), @@ -93,7 +97,7 @@ export function getTimeSeries({ name: variable.name, label: `${i18n.tie(variable.label)} (${Units.getTargetUnitSymbol(baseUnit)})`, unit, - targetUnit: Units.getTargetUnit(unit), + targetUnit, chartjs: Object.assign({ parsing: { xAxisKey: 'time',