From ca0233acf0225825dcc9f2c9541191457a2872d1 Mon Sep 17 00:00:00 2001 From: Lisa Stillwell Date: Wed, 13 Nov 2024 09:41:20 -0500 Subject: [PATCH 1/4] updates to get charts data converted when changing units setting --- src/components/dialog/observation-chart.js | 37 ++++++++++++++++--- .../trays/help-about/helpAboutTray.js | 30 +++++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/components/dialog/observation-chart.js b/src/components/dialog/observation-chart.js index 0709fd4..63ec74f 100644 --- a/src/components/dialog/observation-chart.js +++ b/src/components/dialog/observation-chart.js @@ -7,6 +7,7 @@ import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer, Tool import {getNamespacedEnvParam} from "@utils/map-utils"; import dayjs from 'dayjs'; import { useSettings } from '@context'; +import { metersToFeet } from '@utils/map-utils'; // install day.js for UTC visual formatting const utc = require("dayjs/plugin/utc"); @@ -43,7 +44,7 @@ console.error = (...args) => { * @param url * @returns { json } */ -function getObsChartData(url, setLineButtonView, useUTC) { +function getObsChartData(url, setLineButtonView, useUTC, units) { // configure the retry count to be zero axiosRetry(axios, { retries: 0 @@ -80,7 +81,7 @@ function getObsChartData(url, setLineButtonView, useUTC) { // if there was not an error if (ret_val !== 500) { // return the csv data in JSON format - return csvToJSON(ret_val, setLineButtonView, useUTC); + return csvToJSON(ret_val, setLineButtonView, useUTC, units); } else // just return nothing and nothing will be rendered return ''; @@ -88,13 +89,30 @@ function getObsChartData(url, setLineButtonView, useUTC) { }); } +/** + * converts chart data units from meters to feet + * + * @param { json [] } + * @returns { json [] } + */ +const convertChartDataToFeet = (e) => { + for (const key in e) { + console.log(key); + if (e[key] && key !== "time") { + e[key] = metersToFeet(e[key]); + } + } + + return e; +}; + /** * converts CSV data into json format * * @param csvData * @returns { json [] } */ -const csvToJSON = (csvData, setLineButtonView, useUTC) => { +const csvToJSON = (csvData, setLineButtonView, useUTC, units) => { // ensure that there is csv data to convert if (csvData !== "") { // split on carriage returns. also removing all the windows \r characters if they exist @@ -138,6 +156,11 @@ const csvToJSON = (csvData, setLineButtonView, useUTC) => { e.time = new Date(e.time).toLocaleString(); } + // convert all the chart data to feet, if the units type is imperial + if (units === "imperial") { + e = convertChartDataToFeet(e); + } + // data that is missing a value will not result in plotting if (e["Observations"]) { e["Observations"] = +parseFloat(e["Observations"]).toFixed(3); @@ -334,10 +357,12 @@ function get_xtick_interval(data) { */ const CreateObsChart = (c) => { // get the timezone preference - const { useUTC } = useSettings(); + const { useUTC, unitsType } = useSettings(); + // set the units label + const unitLabel = (unitsType.current === "imperial") ? "ft" : "m"; // call to get the data. expect back some information too - const {status, data} = getObsChartData(c.chartProps.url, c.chartProps.setLineButtonView, useUTC.enabled); + const {status, data} = getObsChartData(c.chartProps.url, c.chartProps.setLineButtonView, useUTC.enabled, unitsType.current); // render the chart return ( @@ -357,7 +382,7 @@ const CreateObsChart = (c) => { - formatY_axis(value)}/> diff --git a/src/components/trays/help-about/helpAboutTray.js b/src/components/trays/help-about/helpAboutTray.js index bee4856..5b1e137 100644 --- a/src/components/trays/help-about/helpAboutTray.js +++ b/src/components/trays/help-about/helpAboutTray.js @@ -657,6 +657,36 @@ export const HelpAboutTray = () => { + { + setIndex(expanded ? 19 : null); + }}> + + How do I change the units of measurement (i.e. meters or feet) displayed on the webpage? + + + + + The Application + Settings sidebar button. + + + + Users have the ability to set the units of measurement used on the webpage in the Application Settings sidebar. + + + + Users can select whether to show data values on the webpage in metric (the default) or imperial values. + In addition from changing between meters and feet, if the user selects imperial units, they can also select between mph and knots for speed type units. + + + + Click on the Application Settings icon button on the sidebar. + Locate the "Units of measurement" section. + Select the appropriate radio buttons for your preferences. + + + + From 3eb0d020a69ea4a8bf1b3e7d6c1f8a4f98ee351f Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Wed, 13 Nov 2024 17:15:34 -0500 Subject: [PATCH 2/4] adding the handling of the measurement units and refactoring for clarity and performance --- src/components/dialog/observation-chart.js | 238 +++++++++------------ 1 file changed, 102 insertions(+), 136 deletions(-) diff --git a/src/components/dialog/observation-chart.js b/src/components/dialog/observation-chart.js index 63ec74f..76cd0fe 100644 --- a/src/components/dialog/observation-chart.js +++ b/src/components/dialog/observation-chart.js @@ -4,10 +4,9 @@ import { Typography } from '@mui/material'; import axios from 'axios'; import axiosRetry from 'axios-retry'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer, Tooltip, ReferenceLine } from 'recharts'; -import {getNamespacedEnvParam} from "@utils/map-utils"; +import {feetToMeters, metersToFeet, getNamespacedEnvParam} from "@utils/map-utils"; import dayjs from 'dayjs'; import { useSettings } from '@context'; -import { metersToFeet } from '@utils/map-utils'; // install day.js for UTC visual formatting const utc = require("dayjs/plugin/utc"); @@ -44,7 +43,7 @@ console.error = (...args) => { * @param url * @returns { json } */ -function getObsChartData(url, setLineButtonView, useUTC, units) { +function getObsChartData(url, setLineButtonView) { // configure the retry count to be zero axiosRetry(axios, { retries: 0 @@ -64,58 +63,33 @@ function getObsChartData(url, setLineButtonView, useUTC, units) { }; // make the call to get the data - const ret_val = await axios - // make the call to get the data - .get(url, requestOptions) + const ret_val = await axios.get(url, requestOptions) // use the data returned - .then((response) => { - // return the data - return response.data; - }) - // otherwise post the issue to the console log - .catch((error) => { - // make sure we do not render anything - return error.response.status; - }); + .then((response) => { return response.data; }) + // otherwise capture the error + .catch((error) => { return error.response.status; }); // if there was not an error - if (ret_val !== 500) { + if (ret_val !== 500) // return the csv data in JSON format - return csvToJSON(ret_val, setLineButtonView, useUTC, units); - } else + return csvToJSON(ret_val, setLineButtonView); + else // just return nothing and nothing will be rendered return ''; }, refetchOnWindowFocus: false }); } -/** - * converts chart data units from meters to feet - * - * @param { json [] } - * @returns { json [] } - */ -const convertChartDataToFeet = (e) => { - for (const key in e) { - console.log(key); - if (e[key] && key !== "time") { - e[key] = metersToFeet(e[key]); - } - } - - return e; -}; - /** * converts CSV data into json format * * @param csvData * @returns { json [] } */ -const csvToJSON = (csvData, setLineButtonView, useUTC, units) => { +const csvToJSON = (csvData, setLineButtonView) => { // ensure that there is csv data to convert if (csvData !== "") { - // split on carriage returns. also removing all the windows \r characters if they exist + // split on carriage returns. also removing all the windows "\r" characters when they exist const lines = csvData.replaceAll('\r', '').split('\n'); // init the result @@ -129,104 +103,95 @@ const csvToJSON = (csvData, setLineButtonView, useUTC, units) => { // split the line on commas const currentLine = lines[i].split(","); - // init the converted data + // init storage for the processed data const jsonObj = {}; - // loop through the data and get name/vale pairs in JSON format + // loop through the data and get name/value pairs in JSON format for (let j = 0; j < dataHeader.length; j++) { // save the data jsonObj[dataHeader[j]] = currentLine[j]; } - // add the data to the return - ret_val.push(jsonObj); - } - - // remove the timezone from the time value - ret_val.map(function (e) { - // only convert records with a valid time - if (e.time !== "") { - // put the date/time in the chosen format - if (useUTC) { - // reformat the text given into UTC format - e.time = e.time.substring(0, e.time.split(':', 2).join(':').length) + 'Z'; - } - else { - // reformat the date/time to the local timezone - e.time = new Date(e.time).toLocaleString(); - } - - // convert all the chart data to feet, if the units type is imperial - if (units === "imperial") { - e = convertChartDataToFeet(e); - } - - // data that is missing a value will not result in plotting - if (e["Observations"]) { - e["Observations"] = +parseFloat(e["Observations"]).toFixed(3); - - // set the line button to be in view - setLineButtonView("Observations"); - } else - e["Observations"] = null; - - if (e["NOAA Tidal Predictions"]) { - e["NOAA Tidal Predictions"] = +parseFloat(e["NOAA Tidal Predictions"]).toFixed(3); - - // set the line button to be in view - setLineButtonView("NOAA Tidal Predictions"); - } else - e["NOAA Tidal Predictions"] = null; - - if (e["APS Nowcast"]) { - e["APS Nowcast"] = +parseFloat(e["APS Nowcast"]).toFixed(3); - - // set the line button to be in view - setLineButtonView("APS Nowcast"); - } else - e["APS Nowcast"] = null; - - if (e["APS Forecast"]) { - e["APS Forecast"] = +parseFloat(e["APS Forecast"]).toFixed(3); - - // set the line button to be in view - setLineButtonView("APS Forecast"); - } else - e["APS Forecast"] = null; + // make sure there is a good record (has a timestamp) + if (jsonObj.time.length) { + // add these so the "units" converter will initially format the data properly + jsonObj['useUTC'] = null; + jsonObj['units'] = null; - if (e["SWAN Nowcast"]) { - e["SWAN Nowcast"] = +parseFloat(e["SWAN Nowcast"]).toFixed(3); - - // set the line button to be in view - setLineButtonView("SWAN Nowcast"); - } else - e["SWAN Nowcast"] = null; + // add the data to the return + ret_val.push(jsonObj); + } + } - if (e["SWAN Forecast"]) { - e["SWAN Forecast"] = +parseFloat(e["SWAN Forecast"]).toFixed(3); + // get the data formatted properly + ret_val.forEach( function (chartItem) { + // loop through the keys + Object.keys(chartItem).forEach(function (key) { + // if there is a value for the key + if (chartItem[key]) + setLineButtonView(key); + // undefined data gets set to null for proper chart rendering + else + chartItem[key] = null; + }); + }); - // set the line button to be in view - setLineButtonView("SWAN Forecast"); - } else - e["SWAN Forecast"] = null; + // return the data + return ret_val; + } +}; - if (e["Difference (APS-OBS)"]) { - e["Difference (APS-OBS)"] = +parseFloat(e["Difference (APS-OBS)"]).toFixed(3); +/** + * reformats the data based on user selections for the timezone and units of measurement + * + * @param data + * @param newUnits + * @param useUTC + */ +const getReformattedData = (data, newUnits, useUTC) => { + // if there is data to process + if (data !== undefined && data.length) { + // loop through each chart data item + data.forEach( function (chartItem) { + // loop through all the keys and change the format if needed + Object.keys(chartItem).forEach(function(key){ + // check for timezone conversion on the time element + if (key === 'time') { + // convert the date/time to UTC format + if (useUTC) { + // get the date/time in ISO format + const newTime = new Date(chartItem[key]).toISOString(); + + // reformat the date/time into the new format + chartItem[key] = newTime.replace('T', ' ') + .substring(0, newTime.split(':', 2) .join(':').length) + 'Z'; + } + // convert the date/time to local timezone + else { + // reformat the date/time to the local timezone + chartItem[key] = new Date(chartItem[key]).toLocaleString(); + } + } + // check for measurement units conversion + else if (newUnits !== chartItem['units']) { + // if the data element is null, it stays null + if (chartItem[key] !== null) { + // convert the value to the new measurement units + chartItem[key] = (newUnits === 'imperial') ? +parseFloat(metersToFeet(chartItem[key])).toFixed(3) : + +parseFloat(feetToMeters(chartItem[key])).toFixed(3); + } + } + }); - // set the line button to be in view - setLineButtonView("Difference (APS-OBS)"); - } else - e["Difference (APS-OBS)"] = null; - } + // save the new timezone and measurement unit types + chartItem['useUTC'] = useUTC; + chartItem['units'] = newUnits; }); - - // return the json data representation - return ret_val; } }; /** - * reformats the data label shown on the x-axis + * reformats the data label shown on the y-axis * * @param value * @returns {string} @@ -249,7 +214,7 @@ function formatX_axis(value, useUTC) { // empty data will be ignored if (value !== "") // put this in the proper format - if(useUTC) + if (useUTC) ret_val = dayjs.utc(value).format('MM/DD-HH').split('+')[0] + 'Z'; // else use reformat using the local time zone else @@ -330,21 +295,18 @@ function get_xtick_interval(data) { let interval = one_hour_interval * 24 - 1; // all ticks for <= 0.5 days> - if (days <= 0.5) { + if (days <= 0.5) interval = 0; - } // hour labels for <= 1.5 days - else if (days <= 1.5) { + else if (days <= 1.5) interval = one_hour_interval - 1; - } // 6-hour labels for <= 4.5 days - else if (days <= 4.5) { + else if (days <= 4.5) interval = one_hour_interval * 6 - 1; - } // 12-hour labels for <= 7.5 days - else if (days <= 7.5) { + else if (days <= 7.5) interval = one_hour_interval * 12 - 1; - } + return interval; } @@ -358,11 +320,15 @@ function get_xtick_interval(data) { const CreateObsChart = (c) => { // get the timezone preference const { useUTC, unitsType } = useSettings(); - // set the units label + + // set the "units" label const unitLabel = (unitsType.current === "imperial") ? "ft" : "m"; // call to get the data. expect back some information too - const {status, data} = getObsChartData(c.chartProps.url, c.chartProps.setLineButtonView, useUTC.enabled, unitsType.current); + const {status, data} = getObsChartData(c.chartProps.url, c.chartProps.setLineButtonView); + + // reformat the data to the desired time zone and units of measurement + getReformattedData(data, unitsType.current, useUTC.enabled); // render the chart return ( @@ -387,19 +353,19 @@ const CreateObsChart = (c) => { - - - - - - - From c8ba8448d1cf97e53674d0abf2735cdd5648cc74 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Thu, 14 Nov 2024 08:16:15 -0500 Subject: [PATCH 3/4] tidying up --- src/components/dialog/observation-chart.js | 24 ++++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/components/dialog/observation-chart.js b/src/components/dialog/observation-chart.js index 76cd0fe..888104c 100644 --- a/src/components/dialog/observation-chart.js +++ b/src/components/dialog/observation-chart.js @@ -17,7 +17,7 @@ dayjs.extend(utc); /** * renders the observations as a chart * - * @param dataUrl + * @param chartProps * @returns React.ReactElement * @constructor */ @@ -27,7 +27,7 @@ export default function ObservationChart(chartProps) { } /** - * this suppresses the re-chart errors on the x/y-axis rendering. + * this captures the re-chart deprecation warnings on the chart rendering. * * @type {{(message?: any, ...optionalParams: any[]): void, (...data: any[]): void}} */ @@ -41,7 +41,8 @@ console.error = (...args) => { * Retrieves and returns the chart data in JSON format * * @param url - * @returns { json } + * @param setLineButtonView + * @returns { [json] || '' } */ function getObsChartData(url, setLineButtonView) { // configure the retry count to be zero @@ -84,6 +85,7 @@ function getObsChartData(url, setLineButtonView) { * converts CSV data into json format * * @param csvData + * @param setLineButtonView * @returns { json [] } */ const csvToJSON = (csvData, setLineButtonView) => { @@ -123,7 +125,7 @@ const csvToJSON = (csvData, setLineButtonView) => { } } - // get the data formatted properly + // set the chart line toggle and get undefined data formatted for the chart rendering ret_val.forEach( function (chartItem) { // loop through the keys Object.keys(chartItem).forEach(function (key) { @@ -142,7 +144,9 @@ const csvToJSON = (csvData, setLineButtonView) => { }; /** - * reformats the data based on user selections for the timezone and units of measurement + * reformats the data based on user selections for the timezone and units of measurement. + * + * note: this method modifies the data in-place. * * @param data * @param newUnits @@ -205,6 +209,7 @@ function formatY_axis(value) { * reformats the data label shown on the x-axis. this uses the chosen timezone. * * @param value + * @param useUTC * @returns {string} */ function formatX_axis(value, useUTC) { @@ -307,13 +312,14 @@ function get_xtick_interval(data) { else if (days <= 7.5) interval = one_hour_interval * 12 - 1; + // return the calculated interval return interval; } /** * Creates the chart. * - * @param url + * @param c: the chart props * @returns React.ReactElement * @constructor */ @@ -336,9 +342,9 @@ const CreateObsChart = (c) => { { status === 'pending' ? (Gathering chart data...) : (status === 'error' || data === '') ? ( - - There was a problem collecting data for this location. - ) : + + There was a problem collecting data for this location. + ) : From 50994cb0eea8887432f05e46b14bed7c0694707f Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Thu, 14 Nov 2024 08:19:45 -0500 Subject: [PATCH 4/4] tidying up --- src/components/trays/help-about/helpAboutTray.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/trays/help-about/helpAboutTray.js b/src/components/trays/help-about/helpAboutTray.js index 5b1e137..e483c95 100644 --- a/src/components/trays/help-about/helpAboutTray.js +++ b/src/components/trays/help-about/helpAboutTray.js @@ -663,8 +663,8 @@ export const HelpAboutTray = () => { How do I change the units of measurement (i.e. meters or feet) displayed on the webpage? - + The Application Settings sidebar button.