Optional Fields:
@@ -171,14 +170,18 @@ export default connect(
const buildContent = (title, csvData, notes) => (
-
-
-
-
-
+ {csvData && (
+ <>
+
+
+
+
+ >
+ )}
+
@@ -214,7 +217,7 @@ export default connect(
Q: I want to try the site with my
own projects. How should I organize my dataset for upload?
-
+
Glad you asked! The site supports .csv uploads of Instruments, Timeseries, Timeseries
measurements and Inclinometer measurements at this time. Uploaders for Projects are coming soon. To get organized,
start with projects {' '}
diff --git a/src/app-pages/instrument/details.jsx b/src/app-pages/instrument/details.jsx
index 7c0786a8..819e5d11 100644
--- a/src/app-pages/instrument/details.jsx
+++ b/src/app-pages/instrument/details.jsx
@@ -5,10 +5,11 @@ import { Edit, Refresh, SettingsOutlined } from '@mui/icons-material';
import AlertEntry from './alert/alert-entry';
import Button from '../../app-components/button';
import Card from '../../app-components/card';
-import DepthBasedPlots from './depth-based-plots';
+import SaaDepthBasedPlots from './saa-depth-based-plots';
import Dropdown from '../../app-components/dropdown';
import InstrumentDisplay from './instrument-display';
import InstrumentForm from '../../common/forms/instrument-form';
+import IpiDepthBasedPlots from './ipi-depth-based-plots';
import LoginMessage from '../../app-components/login-message';
import Map from '../../app-components/classMap';
import NoAlerts from './alert/no-alerts';
@@ -54,12 +55,13 @@ export default connect(
const timeseries = timeseriesByInstrumentId[instrument.id] || [];
const isShapeArray = instrument?.type === 'SAA';
+ const isIPI = instrument?.type === 'IPI';
const len = timeseries.length;
let firstTimeseries = null;
if (len && len > 0) firstTimeseries = timeseries[0];
- if (isShapeArray && !notifcationFired && !instrument?.opts?.initial_time) {
+ if ((isShapeArray || isIPI) && !notifcationFired && !instrument?.opts?.initial_time) {
setNotificationFired(true);
doNotificationFire({
title: 'Missing Initial Time',
@@ -71,7 +73,7 @@ export default connect(
size='small'
variant='info'
text='Set Initial Time'
- handleClick={() => doModalOpen(SetInitialTimeModal, {}, 'lg')}
+ handleClick={() => doModalOpen(SetInitialTimeModal, { type: isShapeArray ? 'saa' : 'ipi'}, 'lg')}
/>
),
@@ -178,7 +180,12 @@ export default connect(
{isShapeArray && (
+ )}
+ {isIPI && (
+
)}
diff --git a/src/app-pages/instrument/ipi-depth-based-plots.jsx b/src/app-pages/instrument/ipi-depth-based-plots.jsx
new file mode 100644
index 00000000..195717d8
--- /dev/null
+++ b/src/app-pages/instrument/ipi-depth-based-plots.jsx
@@ -0,0 +1,252 @@
+import React, { useEffect, useState } from 'react';
+import ReactDatePicker from 'react-datepicker';
+import { Add, Remove } from '@mui/icons-material';
+import { Checkbox, FormControlLabel, Slider, Stack, Switch } from '@mui/material';
+import { addDays, subDays } from 'date-fns';
+import { connect } from 'redux-bundler-react';
+import { DateTime } from 'luxon';
+import { useDeepCompareEffect } from 'react-use';
+
+import Button from '../../app-components/button';
+import Card from '../../app-components/card';
+import Chart from '../../app-components/chart/chart';
+import SetInitialTimeModal from './setInitialTimeModal';
+
+const colors = {
+ init: '#000000',
+};
+
+const config = {
+ repsonsive: true,
+ displaylogo: false,
+ displayModeBar: true,
+ scrollZoom: true,
+};
+
+const layout = (showTemperature, showIncremental) => ({
+ showlegend: true,
+ autosize: true,
+ height: 800,
+ rows: 1,
+ columns: showTemperature ? 2 : 1,
+ yaxis: {
+ domain: [0, 1],
+ anchor: 'x1',
+ autorange: 'reversed',
+ title: `Depth in Feet`,
+ },
+ xaxis: {
+ domain: [0, showTemperature ? 0.4 : 1],
+ anchor: 'y1',
+ title: `${showIncremental ? 'Incremental' : 'Cumulative'} Displacement`,
+ },
+ ...showTemperature && {
+ xaxis2: {
+ title: 'Temperature',
+ domain: [0.6, 1],
+ anchor: 'y2',
+ },
+ yaxis2: {
+ domain: [0, 1],
+ anchor: 'x2',
+ autorange: 'reversed',
+ }
+ },
+});
+
+const formatData = (measurements, indexes, showInitial, showTemperature, showIncremental) => {
+ if (!measurements.length) return {};
+
+ const timeIncrements = measurements.sort((a, b) => DateTime.fromISO(a.time).toMillis() - DateTime.fromISO(b.time).toMillis())
+ const relevantData = timeIncrements.slice(indexes[0], indexes[1] + 1);
+
+ const dataArray = [
+ ...(showInitial ? build2dTrace(timeIncrements[0], true, showTemperature, showIncremental).flat() : []),
+ ...relevantData.map(m => build2dTrace(m, false, showTemperature, showIncremental)).flat(),
+ ].filter(e => e);
+
+ return { dataArray, timeIncrements, relevantData };
+};
+
+const build2dTrace = (data, isInit, showTemperature, showIncremental) => {
+ if (!Object.keys(data).length) return {};
+
+ const { time, measurements } = data;
+
+ const x = [], xTemp = [], y = [];
+
+ measurements?.forEach(element => {
+ x.push(showIncremental ? (element?.inc_dev || 0) : (element?.cum_dev || 0));
+ xTemp.push(element?.temp);
+ y.push(element?.elevation || 0);
+ });
+
+ const localDateString = DateTime.fromISO(time).toLocaleString(DateTime.DATETIME_SHORT);
+ const common = {
+ y,
+ mode: 'markers+lines',
+ marker: { size: 5, color: isInit ? colors['init'] : undefined },
+ line: { width: 1 },
+ type: 'scatter',
+ };
+
+ return [{
+ ...common,
+ x,
+ name: isInit ? `Initial Displacement (${localDateString})` : `Displacement at ${localDateString}`,
+ hovertemplate: `
+ ${localDateString}
+ Elevation: %{y}
+ ${showIncremental ? 'Incremental' : 'Cumulative'} Displacement: %{x}
+
+ `,
+ }, showTemperature ? {
+ ...common,
+ xTemp,
+ xaxis: 'x2',
+ yaxis: 'y2',
+ name: isInit ? `Initial Temperature (${localDateString})` : `Temperature at ${localDateString}`,
+ hovertemplate: `
+ ${localDateString}
+ Elevation: %{y}
+ Temperature: %{x}
+
+ `,
+ } : {}];
+};
+
+const IpiDepthBasedPlots = connect(
+ 'doModalOpen',
+ 'doFetchInstrumentSensorsById',
+ 'doFetchInstrumentSensorMeasurements',
+ 'selectInstrumentSensors',
+ 'selectInstrumentSensorsMeasurements',
+ ({
+ doModalOpen,
+ doFetchInstrumentSensorsById,
+ doFetchInstrumentSensorMeasurements,
+ instrumentSensors,
+ instrumentSensorsMeasurements,
+ }) => {
+ const [isOpen, setIsOpen] = useState(true);
+ const [showTemperature, setShowTemperature] = useState(true);
+ const [showInitial, setShowInitial] = useState(false);
+ const [showIncremental, setShowIncremental] = useState(false);
+ const [sliderVal, setSliderVal] = useState([0, 0]);
+ const [dateRange, setDateRange] = useState([subDays(new Date(), 7), new Date()]);
+
+ const { dataArray = [], timeIncrements = [] } = formatData(instrumentSensorsMeasurements, sliderVal, showInitial, showTemperature, showIncremental);
+
+ useEffect(() => {
+ doFetchInstrumentSensorsById('ipi');
+ }, [doFetchInstrumentSensorsById]);
+
+ useDeepCompareEffect(() => {
+ if (isOpen) {
+ doFetchInstrumentSensorMeasurements('ipi', dateRange[1].toISOString(), dateRange[0].toISOString());
+ }
+ }, [isOpen, dateRange]);
+
+ return (
+
+
+ : }
+ handleClick={() => setIsOpen(!isOpen)}
+ title={isOpen ? 'Collapse Section' : 'Expand Section'}
+ />
+ Depth Based Plots
+
+
+ {isOpen ? (
+ <>
+ {instrumentSensors.length ? (
+ <>
+ Select a timeframe to view plots for the associated timeseries:
+
+
+ Start Date
+ setDateRange([date, addDays(date, 7)])}
+ />
+
+
+ End Date
+ setDateRange([subDays(date, 7), date])}
+ />
+
+
+ setShowInitial(prev => !prev)} />}
+ label='Show Initial Displacement'
+ />
+
+
+ setShowTemperature(prev => !prev)} />}
+ label='Show Temperature'
+ />
+
+
+
+ Cumulative
+ setShowIncremental(prev => !prev)} />
+ Incremental
+
+
+
+ doModalOpen(SetInitialTimeModal, { type: 'ipi' }, 'lg')}
+ />
+
+
+
+
+
+ {DateTime.fromISO(instrumentSensorsMeasurements[val]?.time).toFormat('MMM dd, yyyy hh:mm:ss')} }
+ onChange={(_e, newVal) => setSliderVal(newVal)}
+ />
+
+
+ >
+ ) : No Sensors for this instrument. }
+ >
+ ) : Expand to view depth plots... }
+
+
+ );
+ },
+);
+
+export default IpiDepthBasedPlots;
diff --git a/src/app-pages/instrument/depth-based-plots.jsx b/src/app-pages/instrument/saa-depth-based-plots.jsx
similarity index 96%
rename from src/app-pages/instrument/depth-based-plots.jsx
rename to src/app-pages/instrument/saa-depth-based-plots.jsx
index b18e467d..6c290d86 100644
--- a/src/app-pages/instrument/depth-based-plots.jsx
+++ b/src/app-pages/instrument/saa-depth-based-plots.jsx
@@ -153,7 +153,7 @@ const buildLongTraces = (dataArray, initialMeasurement) => {
}];
};
-const DepthBasedPlots = connect(
+const SaaDepthBasedPlots = connect(
'doModalOpen',
'doFetchInstrumentSensorsById',
'doFetchInstrumentSensorMeasurements',
@@ -173,12 +173,12 @@ const DepthBasedPlots = connect(
const initialMeasurement = instrumentSensorsMeasurements.length ? instrumentSensorsMeasurements[0]?.measurements?.findLast(e => e) : {};
useEffect(() => {
- doFetchInstrumentSensorsById();
+ doFetchInstrumentSensorsById('saa');
}, [doFetchInstrumentSensorsById]);
useDeepCompareEffect(() => {
if (isOpen) {
- doFetchInstrumentSensorMeasurements(dateRange[1].toISOString(), dateRange[0].toISOString());
+ doFetchInstrumentSensorMeasurements('saa', dateRange[1].toISOString(), dateRange[0].toISOString());
}
}, [isOpen, dateRange]);
@@ -225,7 +225,7 @@ const DepthBasedPlots = connect(
Cumulative
setIsIncrement(prev => !prev)} />
- Increment
+ Incremental
@@ -235,7 +235,7 @@ const DepthBasedPlots = connect(
variant='info'
size='small'
text='Set Initial Time'
- handleClick={() => doModalOpen(SetInitialTimeModal, {}, 'lg')}
+ handleClick={() => doModalOpen(SetInitialTimeModal, { type: 'saa' }, 'lg')}
/>
@@ -263,4 +263,4 @@ const DepthBasedPlots = connect(
},
);
-export default DepthBasedPlots;
+export default SaaDepthBasedPlots;
diff --git a/src/app-pages/instrument/sensors/automapSensorModal.jsx b/src/app-pages/instrument/sensors/automapSensorModal.jsx
index 111325cf..be549328 100644
--- a/src/app-pages/instrument/sensors/automapSensorModal.jsx
+++ b/src/app-pages/instrument/sensors/automapSensorModal.jsx
@@ -16,6 +16,7 @@ const AutomapSensorModal = connect(
doUpdateInstrumentSensor,
instrumentSensors,
instrumentTimeseriesItems: timeseries,
+ type,
}) => {
const [overwriteExisting, setOverwriteExisting] = useState(false);
const xyzTimeseries = [];
@@ -79,7 +80,7 @@ const AutomapSensorModal = connect(
formData.push(current);
});
- doUpdateInstrumentSensor(formData);
+ doUpdateInstrumentSensor(type, formData);
};
return (
diff --git a/src/app-pages/instrument/sensors/sensorDetails.jsx b/src/app-pages/instrument/sensors/sensorDetails.jsx
index 154aed8e..cbe17336 100644
--- a/src/app-pages/instrument/sensors/sensorDetails.jsx
+++ b/src/app-pages/instrument/sensors/sensorDetails.jsx
@@ -20,6 +20,7 @@ const SensorDetails = connect(
doUpdateInstrumentSensor,
instrumentTimeseriesItemsByRoute: timeseries,
activeSensor,
+ type,
}) => {
// eslint-disable-next-line no-unused-vars
const { instrument_id, ...rest } = activeSensor || {};
@@ -27,7 +28,7 @@ const SensorDetails = connect(
const tsOpts = generateOptions(timeseries);
const getOption = key => tsOpts.find(opt => opt.value === options[key]?.val);
- const handleSave = () => doUpdateInstrumentSensor([extractState(options)]);
+ const handleSave = () => doUpdateInstrumentSensor(type, [extractState(options)]);
useDeepCompareEffect(() => {
// eslint-disable-next-line no-unused-vars
@@ -41,58 +42,122 @@ const SensorDetails = connect(
{
const [activeSensorId, setActiveSensorId] = useState(null);
const activeSensor = instrumentSensors.find(s => s.id === activeSensorId);
useEffect(() => {
- doFetchInstrumentSensorsById();
+ doFetchInstrumentSensorsById(type);
}, [doFetchInstrumentSensorsById]);
return (
- Each sensor should have four (4) timeseries mapped to them to be used in depth-based plotting. These timeseries should contain
- X, Y, Z and Temperature measurements. Initial values can be autofilled if the format of the timeseries' names is consistent with
- the following example: string_name(n1,n2,n3) for X, Y, Z or string_name(n1,n2) for Temperature.
+ {type === 'saa' && (
+
+ Each sensor should have four (4) timeseries mapped to them to be used in depth-based plotting. These timeseries should contain
+ X, Y, Z and Temperature measurements. Initial values can be autofilled if the format of the timeseries' names is consistent with
+ the following example: string_name(n1,n2,n3) for X, Y, Z or string_name(n1,n2) for Temperature.
+
+ )}
+ {type === 'ipi' && (
+
+ Each sensor should have at a minimum two (2) timeseries mapped to them to be used in depth-based plotting. For calculating the
+ Cumulative Displacement, Length as well as one of Tilt or Incremental Displacement timeseries should be populated. If both Incremental
+ Displacement and Tilt are supplied, Incremental Displacement will be used in the calculation. If Incremental Displacement is not supplied,
+ Tilt and Segment Length are used to calculate displacement. If neither Incremental Displacement nor Tilt exist, Cumulative Displacement
+ cannot be calculated. Temperature is optional and will be used to generate a graph to show Temperature delta alongside the Cumulative Displacement.
+
+ )}
- doModalOpen(AutomapSensorModal, {}, 'lg')}
- />
+ {type !== 'ipi' && (
+ doModalOpen(AutomapSensorModal, { type }, 'lg')}
+ />
+ )}
@@ -52,7 +68,7 @@ const Sensors = connect(
/>
- {!!activeSensor && }
+ {!!activeSensor && }
>
) :
No Sensors for this instrument. }
diff --git a/src/app-pages/instrument/setInitialTimeModal.jsx b/src/app-pages/instrument/setInitialTimeModal.jsx
index ad435df2..3c55d180 100644
--- a/src/app-pages/instrument/setInitialTimeModal.jsx
+++ b/src/app-pages/instrument/setInitialTimeModal.jsx
@@ -22,6 +22,7 @@ const SetInitialTimeModal = connect(
doFetchInstrumentSensorMeasurements,
instrumentsByRoute: instrument,
instrumentSensorsMeasurements: measurements,
+ type,
}) => {
const [dateRange, setDateRange] = useState([subDays(new Date(), 7), new Date()]);
const [selectedMeasurement, setSelectedMeasurement] = useState(null);
@@ -40,7 +41,7 @@ const SetInitialTimeModal = connect(
};
useDeepCompareEffect(() => {
- doFetchInstrumentSensorMeasurements(dateRange[1].toISOString(), dateRange[0].toISOString());
+ doFetchInstrumentSensorMeasurements(type, dateRange[1].toISOString(), dateRange[0].toISOString());
}, [dateRange, doFetchInstrumentSensorMeasurements]);
return (
@@ -78,7 +79,7 @@ const SetInitialTimeModal = connect(
{!!measurements && (
setSelectedMeasurement(e.target.innerText)}
+ onChange={(_e, value) => setSelectedMeasurement(value?.label)}
options={selectOptions}
isOptionEqualToValue={(opt, val) => opt.label === val.label}
renderInput={(params) => }
diff --git a/src/app-pages/instrument/settings.jsx b/src/app-pages/instrument/settings.jsx
index b132925c..788638e6 100644
--- a/src/app-pages/instrument/settings.jsx
+++ b/src/app-pages/instrument/settings.jsx
@@ -22,6 +22,7 @@ export default connect(
const forumlaReady = import.meta.env.VITE_FORMULA_EDITOR === 'true';
const chartReady = import.meta.env.VITE_INSTRUMENT_CHART === 'true';
const isShapeArray = instrument?.type === 'SAA';
+ const isIPI = instrument?.type === 'IPI';
const tabs = [
alertsReady && {
@@ -33,9 +34,9 @@ export default connect(
}, {
title: 'Timeseries',
content: ,
- }, isShapeArray && {
+ }, (isShapeArray || isIPI) && {
title: 'Sensors',
- content: ,
+ content: ,
}, forumlaReady && {
title: 'Formula Editor',
content: ,
diff --git a/src/app-pages/instrument/timeseries/timeseries-form.jsx b/src/app-pages/instrument/timeseries/timeseries-form.jsx
index ccbe8d26..9c19dbdd 100644
--- a/src/app-pages/instrument/timeseries/timeseries-form.jsx
+++ b/src/app-pages/instrument/timeseries/timeseries-form.jsx
@@ -77,8 +77,7 @@ export default connect(
renderInput={(params) => (
setName(e.target.value)} />
)}
- onChange={e => setName(e.target.innerText)}
- // @TODO: check value from this.
+ onChange={(e, value) => setName(value?.label)}
/>
diff --git a/src/common/forms/instrument-form.jsx b/src/common/forms/instrument-form.jsx
index a056e642..0e0cc877 100644
--- a/src/common/forms/instrument-form.jsx
+++ b/src/common/forms/instrument-form.jsx
@@ -218,7 +218,10 @@ export default connect(
Type
setTypeId(val?.id)} domain='instrument_type' />
- {findDomainItem('instrument_type', 'SAA')?.id === type_id && (
+ {(
+ findDomainItem('instrument_type', 'SAA')?.id === type_id ||
+ findDomainItem('instrument_type', 'IPI')?.id === type_id
+ ) && (
Bottom Elevation
@@ -358,4 +361,4 @@ export default connect(
);
}
-);
\ No newline at end of file
+);