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.
+
+
+
+