diff --git a/src/components/dialog/observation-chart.js b/src/components/dialog/observation-chart.js index 0709fd4..888104c 100644 --- a/src/components/dialog/observation-chart.js +++ b/src/components/dialog/observation-chart.js @@ -4,7 +4,7 @@ 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'; @@ -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,9 +41,10 @@ console.error = (...args) => { * Retrieves and returns the chart data in JSON format * * @param url - * @returns { json } + * @param setLineButtonView + * @returns { [json] || '' } */ -function getObsChartData(url, setLineButtonView, useUTC) { +function getObsChartData(url, setLineButtonView) { // configure the retry count to be zero axiosRetry(axios, { retries: 0 @@ -63,25 +64,17 @@ function getObsChartData(url, setLineButtonView, useUTC) { }; // 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); - } else + return csvToJSON(ret_val, setLineButtonView); + else // just return nothing and nothing will be rendered return ''; }, refetchOnWindowFocus: false @@ -92,12 +85,13 @@ function getObsChartData(url, setLineButtonView, useUTC) { * converts CSV data into json format * * @param csvData + * @param setLineButtonView * @returns { json [] } */ -const csvToJSON = (csvData, setLineButtonView, useUTC) => { +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 @@ -111,99 +105,97 @@ const csvToJSON = (csvData, setLineButtonView, useUTC) => { // 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(); - } - - // 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); + // 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; - // 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; - - 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); + // 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) { + // 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. + * + * note: this method modifies the data in-place. + * + * @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} @@ -217,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) { @@ -226,7 +219,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 @@ -307,37 +300,41 @@ 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 the calculated interval return interval; } /** * Creates the chart. * - * @param url + * @param c: the chart props * @returns React.ReactElement * @constructor */ 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); + + // reformat the data to the desired time zone and units of measurement + getReformattedData(data, unitsType.current, useUTC.enabled); // render the chart return ( @@ -345,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. + ) : @@ -357,24 +354,24 @@ 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..e483c95 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. + + + +