diff --git a/src/app-bundles/batch-plot-configurations-bundle.js b/src/app-bundles/batch-plot-configurations-bundle.js index 82314b55..817e7c2e 100644 --- a/src/app-bundles/batch-plot-configurations-bundle.js +++ b/src/app-bundles/batch-plot-configurations-bundle.js @@ -1,6 +1,28 @@ -import createRestBundle from './create-rest-bundle'; import { createSelector } from 'redux-bundler'; +import createRestBundle from './create-rest-bundle'; + +const getTimeseriesFromDisplay = (plotType, display, timeseries) => { + switch (plotType) { + case 'scatter-line': + return timeseries.filter((ts) => + (display?.traces?.map(trace => trace.timeseries_id) || []).includes(ts.id) + ); + case 'bullseye': + return timeseries.filter(ts => + [display?.x_axis_timeseries_id, display?.y_axis_timeseries_id].includes(ts.id) + ); + case 'contour': + return timeseries.filter(ts => + display?.timeseries_ids?.includes(ts.id) + ); + case 'profile': + return display?.instrument_id; + default: + throw new Error(`Invalid Plot Type: ${plotType}. Expected one of: ['scatter-line', 'bullseye', 'contour', 'profile']`); + } +}; + export default createRestBundle({ name: 'batchPlotConfigurations', uid: 'id', @@ -82,11 +104,12 @@ export default createRestBundle({ timeseries.length ) { batchPlotConfigurations.forEach((config) => { - const activeTS = timeseries.filter((ts) => - (config?.display?.traces?.map(trace => trace.timeseries_id) || []).includes(ts.id) - ); - instrumentMap[config.id] = instruments.filter((i) => - activeTS.some((ts) => ts.instrument_id === i.id) + const { plot_type, display } = config || {}; + const activeTS = getTimeseriesFromDisplay(plot_type, display, timeseries); + + instrumentMap[config.id] = plot_type === 'profile' + ? instruments.filter(i => i.id === activeTS) + : instruments.filter(i => activeTS.some((ts) => ts.instrument_id === i.id) ); }); } diff --git a/src/app-bundles/instrument-sensors-bundle.js b/src/app-bundles/instrument-sensors-bundle.js index 3efed25a..7e397648 100644 --- a/src/app-bundles/instrument-sensors-bundle.js +++ b/src/app-bundles/instrument-sensors-bundle.js @@ -31,11 +31,12 @@ export default { selectInstrumentSensorsMeasurements: (state) => state.instrumentSensors.measurements, selectInstrumentSensorsLastFetched: (state) => state.instrumentSensors._lastFetched, - doFetchInstrumentSensorsById: (type) => ({ dispatch, store, apiGet }) => { + doFetchInstrumentSensorsById: (type, id = null) => ({ dispatch, store, apiGet }) => { dispatch({ type: 'INSTRUMENT_SENSORS_BY_ID_FETCH_START' }); - const { instrumentId } = store.selectInstrumentsIdByRoute(); + const { instrumentId } = store.selectInstrumentsIdByRoute() || {}; + const uriId = id || instrumentId; - const url = `/instruments/${type}/${instrumentId}/segments`; + const url = `/instruments/${type}/${uriId}/segments`; apiGet(url, (err, body) => { if (err) { @@ -52,11 +53,13 @@ export default { }); }, - doUpdateInstrumentSensor: (type, formData) => ({ dispatch, store, apiPut }) => { + doUpdateInstrumentSensor: (type, formData, id = null) => ({ dispatch, store, apiPut }) => { dispatch({ type: 'INSTRUMENT_SENSOR_UPDATE_START' }); - const { instrumentId } = store.selectInstrumentsIdByRoute(); - const url = `/instruments/${type}/${instrumentId}/segments`; + const { instrumentId } = store.selectInstrumentsIdByRoute() || {}; + const uriId = id || instrumentId; + + const url = `/instruments/${type}/${uriId}/segments`; apiPut(url, formData, (err, _body) => { if (err) { @@ -68,10 +71,12 @@ export default { }); }, - doFetchInstrumentSensorMeasurements: (type, before, after) => ({ dispatch, store, apiGet }) => { + doFetchInstrumentSensorMeasurements: (type, before, after, id = null) => ({ dispatch, store, apiGet }) => { dispatch({ type: 'SENSOR_MEASUREMENTS_FETCH_START' }); - const { instrumentId } = store.selectInstrumentsIdByRoute(); - const url = `/instruments/${type}/${instrumentId}/measurements?before=${before}&after=${after}`; + const { instrumentId } = store.selectInstrumentsIdByRoute() || {}; + const uriId = id || instrumentId; + + const url = `/instruments/${type}/${uriId}/measurements?before=${before}&after=${after}`; apiGet(url, (err, body) => { if (err) { diff --git a/src/app-pages/instrument/notes.jsx b/src/app-pages/instrument/notes.jsx index 3dcd2a49..40c47f44 100644 --- a/src/app-pages/instrument/notes.jsx +++ b/src/app-pages/instrument/notes.jsx @@ -120,7 +120,7 @@ export default connect( const [isAdding, setIsAdding] = useState(false); const sorted = notes.sort(); - const { id } = profileActive; + const { id } = profileActive || {}; return ( project && ( diff --git a/src/app-pages/project/batch-plotting/batch-plotting.jsx b/src/app-pages/project/batch-plotting/batch-plotting.jsx index 6a75ecbf..60b6964f 100644 --- a/src/app-pages/project/batch-plotting/batch-plotting.jsx +++ b/src/app-pages/project/batch-plotting/batch-plotting.jsx @@ -1,8 +1,8 @@ import React, { useState } from 'react'; import { connect } from 'redux-bundler-react'; import { Engineering } from '@mui/icons-material'; -import { toast } from 'react-toastify'; import { Link } from '@mui/material'; +import { toast } from 'react-toastify'; import BullseyePlot from './chart-content/bullseye-plot.jsx'; import Card from '../../../app-components/card'; @@ -13,9 +13,9 @@ import ProfilePlot from './chart-content/profile-plot.jsx'; import ScatterLinePlot from './chart-content/scatter-line-plot.jsx'; import { downloadFinalReport, useGetReportStatus, useInitializeReportDownload } from '../../../app-services/collections/report-configuration-download.ts'; import { tUpdateSuccess } from '../../../common/helpers/toast-helpers'; -import { titlize } from '../../../common/helpers/utils.js'; import './batch-plotting.scss'; +import { PlotTypeText } from './helper.js'; const BatchPlotting = connect( 'doMapsInitialize', @@ -130,7 +130,7 @@ const BatchPlotting = connect( )} - titlize(s)).join('-')} Plot`} /> + {plot_type === 'scatter-line' && } {plot_type === 'profile' && } diff --git a/src/app-pages/project/batch-plotting/chart-content/_ipi-profile-plot.jsx b/src/app-pages/project/batch-plotting/chart-content/_ipi-profile-plot.jsx new file mode 100644 index 00000000..1a850fc2 --- /dev/null +++ b/src/app-pages/project/batch-plotting/chart-content/_ipi-profile-plot.jsx @@ -0,0 +1,205 @@ +import React, { useState } from 'react'; +import ReactDatePicker from 'react-datepicker'; +import { addDays, subDays } from 'date-fns'; +import { Checkbox, FormControlLabel, Slider, Stack, Switch } from '@mui/material'; +import { connect } from 'redux-bundler-react'; +import { DateTime } from 'luxon'; +import { useDeepCompareEffect } from 'react-use'; + +import Chart from '../../../../app-components/chart/chart'; + +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 IpiProfilePlot = connect( + 'doFetchInstrumentSensorMeasurements', + 'selectInstrumentSensorsMeasurements', + ({ + doFetchInstrumentSensorMeasurements, + instrumentSensorsMeasurements, + instrumentId, + }) => { + 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); + + useDeepCompareEffect(() => { + doFetchInstrumentSensorMeasurements('ipi', dateRange[1].toISOString(), dateRange[0].toISOString(), instrumentId); + }, [dateRange, instrumentId]); + + return ( + <> +
+
+ 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 + +
+
+
+
+ +
+
+
+
+ {DateTime.fromISO(instrumentSensorsMeasurements[val]?.time).toFormat('MMM dd, yyyy hh:mm:ss')}} + onChange={(_e, newVal) => setSliderVal(newVal)} + /> +
+
+ + ); + }, +); + +export default IpiProfilePlot; diff --git a/src/app-pages/project/batch-plotting/chart-content/_saa-profile-plot.jsx b/src/app-pages/project/batch-plotting/chart-content/_saa-profile-plot.jsx new file mode 100644 index 00000000..522664e6 --- /dev/null +++ b/src/app-pages/project/batch-plotting/chart-content/_saa-profile-plot.jsx @@ -0,0 +1,215 @@ +import React, { useState } from 'react'; +import { Slider } from '@mui/material'; +import { DateTime } from 'luxon'; +import { Icon } from '@iconify/react'; + +import Chart from '../../../../app-components/chart/chart'; +import { useGetMeasurementsByInstrumentType } from '../../../../app-services/collections/instrument-timeseries-measurements.ts'; + +const colors = [ + '#800000', + '#000075', + '#e6194B', + '#3cb44b', + '#911eb4', + '#fabed4', +]; + +const formatData = (data = [], indexes = [], _isMetric = false) => { + if (!data.length) return {}; + + const depthIncrements = data.map((datum, i) => { + const { time, measurements = [] } = datum; + + const valueDisplacement = measurements.sort((a, b) => b.elevation - a.elevation); + + return { time, valueDisplacement, colorIndex: i }; + }).sort((a, b) => DateTime.fromISO(a.time).toMillis() - DateTime.fromISO(b.time).toMillis()); + + const relevantData = depthIncrements.slice(indexes[0], indexes[1] + 1); + const dataArray = []; + + for (let i = 0; i < relevantData.length; i++) { + const { valueDisplacement, time, colorIndex } = relevantData[i]; + + dataArray.push( + valueDisplacement.reduce((accum, current, ind) => { + const { elevation, x_increment, y_increment, } = current; + + if (ind === 0) { + accum.nDepth.push(elevation); + accum.aIncrement.push(x_increment); + accum.bIncrement.push(y_increment); + accum.time = DateTime.fromISO(time).toFormat('MMM dd, yyyy hh:mm:ss'); + accum.colorIndex = colorIndex; + } else { + accum.nDepth.push(elevation); + accum.aIncrement.push(accum.aIncrement[ind - 1] + x_increment); + accum.bIncrement.push(accum.bIncrement[ind - 1] + y_increment); + accum.time = DateTime.fromISO(time).toFormat('MMM dd, yyyy hh:mm:ss'); + accum.colorIndex = colorIndex; + } + + return accum; + }, { + nDepth: [], + aIncrement: [], + bIncrement: [], + time: '', + colorIndex: '', + }) + ) + } + + return { depthIncrements, dataArray, relevantData }; +}; + +const build3dTraces = (dataArray, unit) => dataArray.map(data => ( + { + x: data.aIncrement, + y: data.bIncrement, + z: data.nDepth, + mode: 'markers+lines', + marker: { size: 3, color: colors[data.colorIndex % colors.length] }, + line: { width: 1 }, + type: 'scatter3d', + name: `${data.time} Cumulative Displacement (in ${unit})`, + } + + // If client wants A and B Displacement on the 3-D plot, add these back in and adjust function to a forEach using push logic. + // , { + // x: data.aIncrement, + // y: new Array(data.bIncrement.length).fill(0), + // z: data.nDepth, + // mode: 'markers+lines', + // marker: { size: 5, color: 'green' }, + // type: 'scatter3d', + // name: `A Displacement (in ${unit})`, + // }, { + // x: new Array(data.aIncrement.length).fill(0), + // y: data.bIncrement, + // z: data.nDepth, + // mode: 'markers+lines', + // marker: { size: 5, color: 'orange' }, + // type: 'scatter3d', + // name: `B Displacement (in ${unit})` + // } +)); + +const build2dTrace = (dataArray, key, unit) => dataArray.map(data => ( + { + x: data[key], + y: data.nDepth, + mode: 'markers+lines', + marker: { size: 5, color: colors[data.colorIndex % colors.length] }, + line: { width: 1 }, + type: 'scatter', + name: `${key} Displacement (in ${unit})`, + hovertemplate: ` + ${data.time}
+ Depth: %{y}
+ Displacement: %{x}
+ + `, + } +)); + +const SaaProfilePlot = ({ + instrumentId, + instrumentType, +}) => { + const [sliderVal, setSliderVal] = useState([0, 0]); + + const { data: measuremnts, isLoading } = useGetMeasurementsByInstrumentType({ instrumentId, instrumentType }); + + const isMetric = false; + const unit = isMetric ? 'mm' : 'inches'; + const { dataArray = [], depthIncrements = [] } = formatData(measuremnts, sliderVal, isMetric); + + const config = { + repsonsive: true, + displaylogo: false, + displayModeBar: true, + scrollZoom: true, + }; + + const layout3d = { + autosize: true, + height: 800, + scene: { + xaxis: { title: `A-Displacement (in ${unit})` }, + yaxis: { title: `B-Displacement (in ${unit})` }, + zaxis: { title: 'Depth', autorange: 'reversed' }, + }, + legend: { + 'orientation': 'h', + }, + }; + + const layoutTall = (key) => ({ + showlegend: false, + autosize: true, + height: 800, + yaxis: { + autorange: 'reversed', + title: `Depth in Feet`, + }, + xaxis: { + title: `${key}-Displacement in ${unit}`, + }, + }); + + const incrementData = build3dTraces(dataArray, unit); + + return ( + <> + {isLoading ? ( +
+ +
+ ) : ( + <> +
+
+ +
+
+ +
+
+ +
+
+
+
+ {DateTime.fromISO(depthIncrements[val]?.time).toFormat('MMM dd, yyyy hh:mm:ss')}} + onChange={(_e, newVal) => setSliderVal(newVal)} + /> +
+
+ + )} + + ); +}; + +export default SaaProfilePlot; diff --git a/src/app-pages/project/batch-plotting/chart-content/contour-plot.jsx b/src/app-pages/project/batch-plotting/chart-content/contour-plot.jsx index a32af3b9..36065a0d 100644 --- a/src/app-pages/project/batch-plotting/chart-content/contour-plot.jsx +++ b/src/app-pages/project/batch-plotting/chart-content/contour-plot.jsx @@ -1,8 +1,15 @@ -import React from 'react'; +import React, { useMemo, useState } from 'react'; +import ReactDatePicker from 'react-datepicker'; +import { addDays, subDays } from 'date-fns'; +import { Autocomplete, TextField } from '@mui/material'; +import { connect } from 'redux-bundler-react'; +import { DateTime } from 'luxon'; +import { formatMeasurementOptions, formatMeasurementTimestamp, generatePayloadForType } from '../helper.js'; import { Icon } from '@iconify/react'; +import { useDeepCompareEffect } from 'react-use'; import Chart from '../../../../app-components/chart/chart'; -import { useGetContourMeasurements } from '../../../../app-services/collections/instrument-timeseries-measurements.ts'; +import { useGetContourMeasurements, useGetMeasurementTimestamps } from '../../../../app-services/collections/instrument-timeseries-measurements.ts'; const generateContourData = (display, measurements) => { const { time } = display || {}; @@ -16,51 +23,129 @@ const generateContourData = (display, measurements) => { }]; }; -const ContourPlot = ({ - plotConfig, -}) => { - const { project_id, display, id } = plotConfig || {}; - const { time } = display || {}; +const ContourPlot = connect( + 'doSaveBatchPlotConfiguration', + ({ + doSaveBatchPlotConfiguration, + plotConfig, + }) => { + const { project_id, display, id, plot_type, name } = plotConfig || {}; + const { time } = display || {}; - const { data: measurements, isLoading } = useGetContourMeasurements({ projectId: project_id, plotConfigId: id, time: time }); + const [selectedMeasurement, setSelectedMeasurement] = useState(time ? formatMeasurementTimestamp(time) : ''); + const [dateRange, setDateRange] = useState([subDays(Date.now(), 7), new Date()]); - const config = { - repsonsive: true, - displaylogo: false, - displayModeBar: true, - scrollZoom: true, - }; + const { data: measurements, isLoading } = useGetContourMeasurements({ projectId: project_id, plotConfigId: id, time: time }); + const { data: timestamps } = useGetMeasurementTimestamps({ + projectId: project_id, + plotConfigId: id, + before: DateTime.fromJSDate(dateRange[1]).toISO(), + after: DateTime.fromJSDate(dateRange[0]).toISO(), + }); - const layout = { - showlegend: false, - autosize: true, - height: 600, - yaxis: { - title: `Latitude`, - }, - xaxis: { - title: `Longitude`, - }, - }; + const measurementOptions = useMemo(() => formatMeasurementOptions(timestamps?.length ? timestamps : []), [timestamps]); - const data = generateContourData(display, measurements); + const config = { + repsonsive: true, + displaylogo: false, + displayModeBar: true, + scrollZoom: true, + }; - return ( - <> - {isLoading ? ( -
- -
- ) : ( - - )} - - ); -}; + const layout = { + showlegend: false, + autosize: true, + height: 600, + yaxis: { + title: `Latitude`, + }, + xaxis: { + title: `Longitude`, + }, + }; + + const data = generateContourData(display, measurements); + + useDeepCompareEffect(() => { + if (selectedMeasurement) { + doSaveBatchPlotConfiguration( + plot_type, + id, + generatePayloadForType( + plot_type, + name, + { + ...display, + time: selectedMeasurement._original, + }, + project_id + ) + ); + } + }, [selectedMeasurement]); + + return ( + <> + {isLoading ? ( +
+ +
+ ) : ( + <> + {id && ( + <> +
+
+ Start Date + setDateRange([date, addDays(date, 7)])} + /> +
+
+ End Date + setDateRange([subDays(date, 7), date])} + /> +
+
+ setSelectedMeasurement(value)} + options={measurementOptions} + isOptionEqualToValue={(opt, val) => opt._original === val._original} + renderInput={(params) => ( + + )} + /> +
+ + )} + + + )} + + ); + }, +); export default ContourPlot; diff --git a/src/app-pages/project/batch-plotting/chart-content/profile-plot.jsx b/src/app-pages/project/batch-plotting/chart-content/profile-plot.jsx index 457f8db7..9dbebfdd 100644 --- a/src/app-pages/project/batch-plotting/chart-content/profile-plot.jsx +++ b/src/app-pages/project/batch-plotting/chart-content/profile-plot.jsx @@ -1,213 +1,21 @@ -import React, { useState } from 'react'; -import { Slider } from '@mui/material'; -import { DateTime } from 'luxon'; -import { Icon } from '@iconify/react'; +import React from 'react'; -import Chart from '../../../../app-components/chart/chart'; -import { useGetMeasurementsByInstrumentType } from '../../../../app-services/collections/instrument-timeseries-measurements.ts'; - -const colors = [ - '#800000', - '#000075', - '#e6194B', - '#3cb44b', - '#911eb4', - '#fabed4', -]; - -const formatData = (data = [], indexes = [], _isMetric = false) => { - if (!data.length) return {}; - - const depthIncrements = data.map((datum, i) => { - const { time, measurements = [] } = datum; - - const valueDisplacement = measurements.sort((a, b) => b.elevation - a.elevation); - - return { time, valueDisplacement, colorIndex: i }; - }).sort((a, b) => DateTime.fromISO(a.time).toMillis() - DateTime.fromISO(b.time).toMillis()); - - const relevantData = depthIncrements.slice(indexes[0], indexes[1] + 1); - const dataArray = []; - - for (let i = 0; i < relevantData.length; i++) { - const { valueDisplacement, time, colorIndex } = relevantData[i]; - - dataArray.push( - valueDisplacement.reduce((accum, current, ind) => { - const { elevation, x_increment, y_increment, } = current; - - if (ind === 0) { - accum.nDepth.push(elevation); - accum.aIncrement.push(x_increment); - accum.bIncrement.push(y_increment); - accum.time = DateTime.fromISO(time).toFormat('MMM dd, yyyy hh:mm:ss'); - accum.colorIndex = colorIndex; - } else { - accum.nDepth.push(elevation); - accum.aIncrement.push(accum.aIncrement[ind - 1] + x_increment); - accum.bIncrement.push(accum.bIncrement[ind - 1] + y_increment); - accum.time = DateTime.fromISO(time).toFormat('MMM dd, yyyy hh:mm:ss'); - accum.colorIndex = colorIndex; - } - - return accum; - }, { - nDepth: [], - aIncrement: [], - bIncrement: [], - time: '', - colorIndex: '', - }) - ) - } - - return { depthIncrements, dataArray, relevantData }; -}; - -const build3dTraces = (dataArray, unit) => dataArray.map(data => ( - { - x: data.aIncrement, - y: data.bIncrement, - z: data.nDepth, - mode: 'markers+lines', - marker: { size: 3, color: colors[data.colorIndex % colors.length] }, - line: { width: 1 }, - type: 'scatter3d', - name: `${data.time} Cumulative Displacement (in ${unit})`, - } - - // If client wants A and B Displacement on the 3-D plot, add these back in and adjust function to a forEach using push logic. - // , { - // x: data.aIncrement, - // y: new Array(data.bIncrement.length).fill(0), - // z: data.nDepth, - // mode: 'markers+lines', - // marker: { size: 5, color: 'green' }, - // type: 'scatter3d', - // name: `A Displacement (in ${unit})`, - // }, { - // x: new Array(data.aIncrement.length).fill(0), - // y: data.bIncrement, - // z: data.nDepth, - // mode: 'markers+lines', - // marker: { size: 5, color: 'orange' }, - // type: 'scatter3d', - // name: `B Displacement (in ${unit})` - // } -)); - -const build2dTrace = (dataArray, key, unit) => dataArray.map(data => ( - { - x: data[key], - y: data.nDepth, - mode: 'markers+lines', - marker: { size: 5, color: colors[data.colorIndex % colors.length] }, - line: { width: 1 }, - type: 'scatter', - name: `${key} Displacement (in ${unit})`, - hovertemplate: ` - ${data.time}
- Depth: %{y}
- Displacement: %{x}
- - `, - } -)); +import SaaProfilePlot from './_saa-profile-plot'; +import IpiProfilePlot from './_ipi-profile-plot'; const ProfilePlot = ({ plotConfig, }) => { - const [sliderVal, setSliderVal] = useState([0, 0]); const { display } = plotConfig || {}; - const { instrument_id, instrument_type = 'SAA' } = display || {}; - - const { data: measuremnts, isLoading } = useGetMeasurementsByInstrumentType({ instrumentId: instrument_id, instrumentType: instrument_type }); - - const isMetric = false; - const unit = isMetric ? 'mm' : 'inches'; - const { dataArray = [], depthIncrements = [] } = formatData(measuremnts, sliderVal, isMetric); - - const config = { - repsonsive: true, - displaylogo: false, - displayModeBar: true, - scrollZoom: true, - }; - - const layout3d = { - autosize: true, - height: 800, - scene: { - xaxis: { title: `A-Displacement (in ${unit})` }, - yaxis: { title: `B-Displacement (in ${unit})` }, - zaxis: { title: 'Depth', autorange: 'reversed' }, - }, - legend: { - 'orientation': 'h', - }, - }; - - const layoutTall = (key) => ({ - showlegend: false, - autosize: true, - height: 800, - yaxis: { - autorange: 'reversed', - title: `Depth in Feet`, - }, - xaxis: { - title: `${key}-Displacement in ${unit}`, - }, - }); - - const incrementData = build3dTraces(dataArray, unit); + const { instrument_id, instrument_type } = display || {}; return ( <> - {isLoading ? ( -
- -
- ) : ( - <> -
-
- -
-
- -
-
- -
-
-
-
- {DateTime.fromISO(depthIncrements[val]?.time).toFormat('MMM dd, yyyy hh:mm:ss')}} - onChange={(_e, newVal) => setSliderVal(newVal)} - /> -
-
- + {instrument_type === 'SAA' && ( + + )} + {instrument_type === 'IPI' && ( + )} ); diff --git a/src/app-pages/project/batch-plotting/components/data-configuration.jsx b/src/app-pages/project/batch-plotting/components/data-configuration.jsx index e4fa2609..7216256b 100644 --- a/src/app-pages/project/batch-plotting/components/data-configuration.jsx +++ b/src/app-pages/project/batch-plotting/components/data-configuration.jsx @@ -9,6 +9,7 @@ import DeleteButton from '../../../../app-components/delete-confirm'; import usePrevious from '../../../../customHooks/usePrevious'; import '../batch-plotting.scss'; +import { PlotTypeText } from '../helper'; // const groupBatchPlotConfigurations = batchPlotConfigurationsItems => { // return batchPlotConfigurationsItems.reduce((accum, current) => { @@ -86,7 +87,7 @@ const DataConfiguration = connect( size='small' options={batchPlotConfigurationsItems.sort((a, b) => -b.name.localeCompare(a.name))} isOptionEqualToValue={(opt, val) => val ? opt?.id === val?.id : false} - groupBy={option => option.plot_type} + groupBy={option => PlotTypeText[option.plot_type]} onChange={(_e, val) => handleSelectChange(val)} getOptionLabel={option => option.name} sx={{ width: 350, display: 'inline-block' }} diff --git a/src/app-pages/project/batch-plotting/helper.js b/src/app-pages/project/batch-plotting/helper.js index 10e184a2..5dc51dca 100644 --- a/src/app-pages/project/batch-plotting/helper.js +++ b/src/app-pages/project/batch-plotting/helper.js @@ -1,5 +1,12 @@ -import { startOfDay, subDays } from "date-fns"; -import { DateTime } from "luxon"; +import { startOfDay, subDays } from 'date-fns'; +import { DateTime } from 'luxon'; + +export const PlotTypeText = { + 'scatter-line': 'Scatter Line Plot', + 'profile': 'Depth Profile Plot', + 'contour': 'Contour Plot', + 'bullseye': 'Bullseye Plot', +}; const getStyle = (trace) => { const { color, line_style, show_markers, width, y_axis } = trace; diff --git a/src/app-pages/project/batch-plotting/modals/BatchPlotConfiguration.jsx b/src/app-pages/project/batch-plotting/modals/BatchPlotConfiguration.jsx index 4d9597c6..a5dbb843 100644 --- a/src/app-pages/project/batch-plotting/modals/BatchPlotConfiguration.jsx +++ b/src/app-pages/project/batch-plotting/modals/BatchPlotConfiguration.jsx @@ -58,7 +58,7 @@ const BatchPlotConfigurationModal = connect( isOptionEqualToValue={(opt, val) => val ? opt?.value === val : false} options={[ { value: 'scatter-line', label: 'Scatter Line' }, - { value: 'profile', label: 'Profile' }, + { value: 'profile', label: 'Depth Profile' }, { value: 'contour', label: 'Contour' }, { value: 'bullseye', label: 'Bullseye' }, ]} diff --git a/src/app-pages/project/batch-plotting/modals/components/_bullseyeDisplay.jsx b/src/app-pages/project/batch-plotting/modals/components/_bullseyeDisplay.jsx index e9e1c495..56edebe1 100644 --- a/src/app-pages/project/batch-plotting/modals/components/_bullseyeDisplay.jsx +++ b/src/app-pages/project/batch-plotting/modals/components/_bullseyeDisplay.jsx @@ -1,8 +1,9 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { Autocomplete, TextField } from '@mui/material'; import { connect } from 'redux-bundler-react'; import { formatTimeseriesOptions, formatTimeseriesId } from '../../helper'; +import { useDeepCompareEffect } from 'react-use'; const formatDisplay = (xTimeseries, yTimeseries, display) => { return { @@ -25,10 +26,10 @@ const BullseyeDisplay = connect( const [xTimeseries, setXTimeseries] = useState(formatTimeseriesId(x_axis_timeseries_id, timeseries)); const [yTimeseries, setYTimeseries] = useState(formatTimeseriesId(y_axis_timeseries_id, timeseries)); - useEffect(() => { + useDeepCompareEffect(() => { const newDisplay = formatDisplay(xTimeseries, yTimeseries, display); onChange(newDisplay, (!!xTimeseries && !!yTimeseries)); - }, [xTimeseries, yTimeseries, display, onChange]); + }, [xTimeseries, yTimeseries, onChange]); return ( <> @@ -58,7 +59,7 @@ const BullseyeDisplay = connect( options={options} value={yTimeseries} isOptionEqualToValue={(opt, val) => val ? opt?.value === val?.value : false} - onChange={(e, val) => setYTimeseries(val.value)} + onChange={(e, val) => setYTimeseries(val)} renderInput={(params) => ( { const { @@ -17,12 +13,10 @@ const formatDisplay = (oldDisplay, newOptions) => { isGradientSmoothing, isShowLabels, selectedTimeseries, - selectedMeasurement, } = newOptions; return { ...oldDisplay, - time: selectedMeasurement._original, timeseries_ids: selectedTimeseries?.map(t => t.value), show_labels: isShowLabels, contour_smoothing: isContourSmoothing, @@ -35,30 +29,18 @@ const ContourDisplay = connect( 'selectInstrumentTimeseriesItems', ({ instrumentTimeseriesItems: timeseries, - initConfig, display, onChange, }) => { - const { id, project_id } = initConfig || {}; - const { time, contour_smoothing, gradient_smoothing, show_labels, locf_backfill, timeseries_ids } = display || {}; + const { contour_smoothing, gradient_smoothing, show_labels, locf_backfill, timeseries_ids } = display || {}; const [selectedDuration, setSelectedDuration] = useState(locf_backfill || ''); const [isContourSmoothing, setIsContourSmoothing] = useState(contour_smoothing || false); const [isGradientSmoothing, setIsGradientSmoothing] = useState(gradient_smoothing || false); const [isShowLabels, setIsShowLabels] = useState(show_labels || false); const [selectedTimeseries, setSelectedTimeseries] = useState(timeseries_ids ? timeseries_ids.map(id => formatTimeseriesId(id, timeseries)) : []); - const [selectedMeasurement, setSelectedMeasurement] = useState(time ? formatMeasurementTimestamp(time) : ''); - const [dateRange, setDateRange] = useState([subDays(Date.now(), 7), new Date()]); - - const { data: timestamps } = useGetMeasurementTimestamps({ - projectId: project_id, - plotConfigId: id, - before: DateTime.fromJSDate(dateRange[1]).toISO(), - after: DateTime.fromJSDate(dateRange[0]).toISO(), - }); const timeseriesOptions = useMemo(() => formatTimeseriesOptions(timeseries).sort((a, b) => -b.label.localeCompare(a.label)), [timeseries]); - const measurementOptions = useMemo(() => formatMeasurementOptions(timestamps?.length ? timestamps : []), [timestamps]); useDeepCompareEffect(() => { const newDisplay = formatDisplay(display, { @@ -67,18 +49,16 @@ const ContourDisplay = connect( isGradientSmoothing, isShowLabels, selectedTimeseries, - selectedMeasurement, }); if (!isEqual(newDisplay, display)) { - onChange(newDisplay, (!!selectedMeasurement && !!selectedDuration && !!selectedTimeseries?.length)); + onChange(newDisplay, (!!selectedDuration && !!selectedTimeseries?.length)); } - }, [selectedMeasurement, selectedDuration, selectedTimeseries, isShowLabels, isContourSmoothing, isGradientSmoothing, onChange]); + }, [selectedDuration, selectedTimeseries, isShowLabels, isContourSmoothing, isGradientSmoothing, onChange]); return ( <>
- {/* @TODO: Should probably save display on change, to make sure measurements reflect selected TS */} - -
-
-
- Start Date - setDateRange([date, addDays(date, 7)])} - /> -
-
- End Date - setDateRange([subDays(date, 7), date])} - /> -
-
- setSelectedMeasurement(value)} - options={measurementOptions} - isOptionEqualToValue={(opt, val) => opt._original === val._original} - renderInput={(params) => ( - - )} - /> ); }, diff --git a/src/app-services/collections/instrument-timeseries-measurements.ts b/src/app-services/collections/instrument-timeseries-measurements.ts index 27a38245..8ef63488 100644 --- a/src/app-services/collections/instrument-timeseries-measurements.ts +++ b/src/app-services/collections/instrument-timeseries-measurements.ts @@ -2,6 +2,12 @@ import { useQuery } from '@tanstack/react-query'; import { apiGet } from '../fetch-helpers'; +type TimeseriesSelectValue = { + label: string, + value: string, + instrumentName: string +} + interface InstrumentParams { instrumentId: string, instrumentType: string, @@ -19,6 +25,7 @@ interface ContourParams extends BullseyeParams { interface TimestampParams extends BullseyeParams { before?: string, after?: string, + selectedTimeseries?: TimeseriesSelectValue[], } export const useGetContourMeasurements = ({ projectId, plotConfigId, time }: ContourParams, opts: ClientQueryOptions) => {