= ({options, dataParams
}
chart.update()
- }, [chart, dataParams, options])
+ }, [chart, dataParams])
return()
}
diff --git a/src/components/charts/Column.jsx b/src/components/charts/Column.jsx
new file mode 100644
index 0000000..a278b7e
--- /dev/null
+++ b/src/components/charts/Column.jsx
@@ -0,0 +1,91 @@
+import { useState, useEffect } from 'react'
+import { ChartTimeBase } from './ChartTimeBase'
+
+const chartPluginsOptions = {
+ plugins: {
+ datalabels: {
+ backgroundColor: 'transparent',
+ borderRadius: 4,
+ color: '#000B',
+ display: context => (context.dataset.label === 'wellDepth') && 'auto',
+ formatter: value => `${value.y.toLocaleTimeString()} ${value.label.toPrecision(4)}`,
+ padding: 6,
+ align: 'left',
+ anchor: 'center',
+ clip: true
+ },
+ legend: { display: false },
+ tooltip: { enable: true }
+ }
+}
+
+const GetRandomColor = () => '#' + Math.floor(Math.random()*(16**6-1)).toString(16)
+
+export const GetOrCreateDatasetByLineConfig = (data, lineConfig) => {
+ let dataset = data?.datasets.find(d => d.label === lineConfig.label)
+ if(!dataset) {
+ let color = lineConfig.borderColor
+ ?? lineConfig.backgroundColor
+ ?? lineConfig.color
+ ?? GetRandomColor()
+
+ dataset = {
+ label: lineConfig.label,
+ data: [],
+ backgroundColor: lineConfig.backgroundColor ?? color,
+ borderColor: lineConfig.borderColor ?? color,
+ borderWidth: lineConfig.borderWidth ?? 1,
+ borderDash: lineConfig.dash ?? [],
+ showLine: lineConfig.showLine ?? !lineConfig.isShape,
+ fill: lineConfig.fill ?? (lineConfig.isShape ? 'shape' : 'none')
+ }
+
+ data.datasets.push(dataset)
+ }
+ return dataset
+}
+
+export const Column = ({ lineGroup, data, postParsing, interval, yDisplay, yStart, pointCount, savePreviousData }) => {
+ const [dataParams, setDataParams] = useState({data: {datasets:[]}, yStart, })
+
+ useEffect(()=>{
+ if((lineGroup.length === 0) || (data.length === 0)) return
+
+ setDataParams((preDataParams) => {
+ lineGroup.forEach(lineCfg => {
+ const dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg)
+ let points = data.map(dataItem => ({
+ x: lineCfg.xConstValue ?? dataItem[lineCfg.xAccessorName],
+ label: dataItem[lineCfg.xAccessorName],
+ y: new Date(dataItem[lineCfg.yAccessorName]),
+ depth: dataItem.wellDepth
+ })).filter(point => (point.x ?? null) !== null && (point.y ?? null) !== null)
+
+ if (savePreviousData)
+ points = [...dataset.data, ...points]
+
+ if(points?.length > 2)
+ points.sort((a,b) => a.y > b.y ? 1 : -1)
+ if(points.length > pointCount)
+ points.splice(0, (pointCount - points.length))
+
+ dataset.data = points
+ })
+
+ preDataParams.yStart = yStart
+ preDataParams.yInterval = interval
+ preDataParams.displayLabels = yDisplay
+
+ postParsing?.(preDataParams)
+ return {...preDataParams}
+ })
+
+ }, [data, lineGroup, interval, yDisplay, yStart, postParsing, pointCount, savePreviousData])
+
+ return
+}
+
+Column.defaultProps = {
+ pointCount: 2048,
+ savePreviousData: false
+}
diff --git a/src/pages/Archive.jsx b/src/pages/Archive.jsx
deleted file mode 100644
index 8abd9de..0000000
--- a/src/pages/Archive.jsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { useState, useEffect } from 'react'
-import { DatePicker } from 'antd'
-import { TelemetryDataSaubService } from '../services/api'
-import moment from 'moment'
-import { invokeWebApiWrapperAsync } from "../components/factory"
-import LoaderPortal from '../components/LoaderPortal'
-import { Grid, GridItem } from '../components/Grid'
-import { Column } from './TelemetryView/Column'
-import { paramsGroups } from './TelemetryView'
-
-const { RangePicker } = DatePicker
-
-export default function Archive({idWell}) {
- const [dataSaub, setDataSaub] = useState([])
- const [rangeDate, setRangeDate] = useState([moment().subtract(3,'hours'), moment()])
- const [showLoader, setShowLoader] = useState(false)
- const [chartInterval, setChartInterval] = useState(600)
-
- const onChangeRange = (range) => {
- setRangeDate(range)
- }
-
- useEffect(() => invokeWebApiWrapperAsync(
- async () => {
- const interval = (rangeDate[1] - rangeDate[0]) / 1000
- let startDate = rangeDate[0].toISOString()
-
- const data = await TelemetryDataSaubService.getData(idWell, startDate, interval, 2048)
- data?.sort((a, b) => a.date > b.date ? 1 : -1)
- setDataSaub(data ?? [])
- setChartInterval(interval)
- },
- setShowLoader,
- `Не удалось загрузить данные по скважине "${idWell}" c ${rangeDate[0]} по ${rangeDate[1]}`
- ), [idWell, rangeDate])
-
- return (
- <>
-
-
- console.log(e)}>
- {paramsGroups.map((group, index) => (
-
-
-
- ))}
-
-
- >
- )
-}
diff --git a/src/pages/Archive/ArchiveColumn.jsx b/src/pages/Archive/ArchiveColumn.jsx
new file mode 100644
index 0000000..b3e7909
--- /dev/null
+++ b/src/pages/Archive/ArchiveColumn.jsx
@@ -0,0 +1,29 @@
+import { Grid, GridItem } from '../../components/Grid'
+import { Column } from '../../components/charts/Column'
+
+export const ArchiveColumn = ({ lineGroup, data, interval, style, headerHeight, yStart }) => {
+ const dataLast = data?.[data.length - 1]
+ const pv = lineGroup.filter(line => line.showLabels).map(line => ({
+ color: line.color,
+ label: line.label,
+ unit: line.units,
+ value: dataLast?.[line.xAccessorName]
+ }))
+
+ return (
+
+
+ {pv?.map((v, idx) => (
+ {v.label}
+ ))}
+
+
+
+ )
+}
diff --git a/src/pages/Archive/ArchiveDisplay.jsx b/src/pages/Archive/ArchiveDisplay.jsx
new file mode 100644
index 0000000..54db8b7
--- /dev/null
+++ b/src/pages/Archive/ArchiveDisplay.jsx
@@ -0,0 +1,30 @@
+import { useEffect, useState } from 'react'
+import { Grid, GridItem } from '../../components/Grid'
+import { ArchiveColumn } from './ArchiveColumn'
+import { paramsGroups } from '../TelemetryView'
+
+export const ArchiveDisplay = ({data, startDate, interval, onWheel}) => {
+ const [chartData, setChartData] = useState([])
+
+ useEffect(() => {
+ const endDate = new Date(+startDate + interval)
+ setChartData(data.filter(elm => elm.date >= startDate && elm.date <= endDate))
+ }, [data, startDate, interval])
+
+ return (
+
+ {paramsGroups.map((group, index) => (
+
+
+
+ ))}
+
+ )
+}
diff --git a/src/pages/Archive/index.jsx b/src/pages/Archive/index.jsx
new file mode 100644
index 0000000..2ee941a
--- /dev/null
+++ b/src/pages/Archive/index.jsx
@@ -0,0 +1,140 @@
+import moment from 'moment'
+import { DatePicker } from 'antd'
+import { useState, useEffect } from 'react'
+import { TelemetryDataSaubService } from '../../services/api'
+import { invokeWebApiWrapperAsync } from '../../components/factory'
+import LoaderPortal from '../../components/LoaderPortal'
+import { Flex } from '../../components/Grid'
+import { PeriodPicker, defaultPeriod } from '../../components/PeriodPicker'
+import { ArchiveDisplay } from './ArchiveDisplay'
+
+const DATA_COUNT = 2048 // Колличество точек на подгрузку графика
+const ADDITIVE_PAGES = 2 // Дополнительные данные для графиков
+const LOADING_TRIGGER = 0.5
+const MOUSE_SENSITIVITY = 1 / 530
+
+const getLoadingInterval = (loaded, startDate, interval) => {
+ // Если данные загружены и дата не заходит за тригер дозагрузка не требуется
+ if (
+ loaded &&
+ +startDate - interval * LOADING_TRIGGER > loaded.start &&
+ +startDate + interval * (LOADING_TRIGGER + 1) < loaded.end
+ )
+ return { loadingStartDate: startDate, newLoaded: loaded, loadingInterval: 0 }
+
+ let loadingStartDate = new Date(+startDate - interval * ADDITIVE_PAGES)
+ let loadingEndDate = new Date(+startDate + interval * (ADDITIVE_PAGES + 1))
+
+ const newLoaded = {
+ start: loadingStartDate,
+ end: loadingEndDate
+ }
+
+ if (loaded) {
+ if (loadingStartDate >= loaded.start)
+ loadingStartDate = new Date(loaded.end)
+ if (loadingEndDate <= loaded.end)
+ loadingEndDate = new Date(loaded.start)
+ newLoaded.start = new Date(Math.min(loaded.start, loadingStartDate))
+ newLoaded.end = new Date(Math.max(loaded.end, loadingEndDate))
+ }
+
+ const loadingInterval = Math.trunc((loadingEndDate - loadingStartDate) / 1000)
+
+ return {
+ loadingStartDate,
+ newLoaded,
+ loadingInterval
+ }
+}
+
+export default function Archive({idWell}) {
+ const [dataSaub, setDataSaub] = useState([])
+ const [chartInterval, setChartInterval] = useState(parseInt(defaultPeriod) * 1000)
+ const [startDate, setStartDate] = useState(new Date(+new Date() - chartInterval))
+ const [showLoader, setShowLoader] = useState(false)
+ const [loaded, setLoaded] = useState(null)
+ const [dateMinLimit, setDateMinLimit] = useState(0)
+
+ const onGraphWheel = (e) => {
+ if (loaded) {
+ setStartDate((prevStartDate) => {
+ const offset = e.deltaY * chartInterval * MOUSE_SENSITIVITY
+ const nextStartDate = new Date(+prevStartDate + offset)
+ return new Date(Math.max(loaded.start, Math.min(nextStartDate, +loaded.end - chartInterval)))
+ })
+ }
+ }
+
+ useEffect(() => invokeWebApiWrapperAsync(
+ async () => {
+ const dates = await TelemetryDataSaubService.getDataDatesRange(idWell)
+ setDateMinLimit(new Date(dates?.from ?? 0))
+ setStartDate(new Date(+new Date(dates?.to ?? new Date()) - chartInterval))
+ },
+ setShowLoader,
+ `Не удалось загрузить диапозон телеметрии для скважины "${idWell}"`
+ ), [])
+
+ useEffect(() => {
+ setStartDate((startDate) => new Date(Math.min(+new Date() - chartInterval, startDate)))
+ }, [chartInterval])
+
+ useEffect(() => {
+ if (showLoader) return
+ const { loadingStartDate, loadingInterval, newLoaded } = getLoadingInterval(loaded, startDate, chartInterval)
+ if (loadingInterval <= 0) return
+ invokeWebApiWrapperAsync(
+ async () => {
+ const data = await TelemetryDataSaubService.getData(idWell, loadingStartDate.toISOString(), loadingInterval, DATA_COUNT)
+
+ const loadedStartDate = new Date(Math.max(+newLoaded.start, +startDate - chartInterval * ADDITIVE_PAGES))
+ const loadedEndDate = new Date(Math.min(+newLoaded.end, +startDate + chartInterval * (ADDITIVE_PAGES + 1)))
+ setLoaded({ start: loadedStartDate, end: loadedEndDate })
+
+ if (data) {
+ data.forEach(elm => elm.date = new Date(elm.date))
+ setDataSaub((prevDataSaub) => {
+ const newData = [...prevDataSaub, ...data]
+ newData.sort((a, b) => a.date > b.date ? 1 : -1)
+ return newData.filter(val => {
+ const date = new Date(val.date)
+ return loadedStartDate < date && date < loadedEndDate
+ })
+ })
+ }
+
+ },
+ setShowLoader,
+ `Не удалось загрузить данные по скважине "${idWell}" c ${startDate.toISOString()} по ${new Date(+startDate + chartInterval).toISOString()}`
+ )
+ }, [idWell, chartInterval, loaded, startDate])
+
+ return (
+ <>
+
+
+ Начальная дата:
+ setStartDate(new Date(startDate))}
+ value={moment(startDate)}
+ />
+
+
+ Период:
+
setChartInterval(parseInt(val) * 1000)} />
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/pages/TelemetryView/Column.jsx b/src/pages/TelemetryView/Column.jsx
deleted file mode 100644
index d67194e..0000000
--- a/src/pages/TelemetryView/Column.jsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import { useState, useEffect } from 'react'
-import { Grid, GridItem } from '../../components/Grid'
-import { ChartTimeBase } from '../../components/charts/ChartTimeBase'
-import { ChartTimeOnlineFooter } from './ChartTimeOnlineFooter'
-
-const stroke = (sz = '2px', c = 'white') => ({ textShadow: `-${sz} -${sz} 0 ${c}, ${sz} -${sz} 0 ${c}, -${sz} ${sz} 0 ${c}, ${sz} ${sz} 0 ${c}` })
-
-const chartPluginsOptions = {
- plugins: {
- datalabels: {
- backgroundColor: 'transparent',
- borderRadius: 4,
- color: '#000B',
- display: context => (context.dataset.label === 'wellDepth') && 'auto',
- formatter: value => `${value.y.toLocaleTimeString()} ${value.label.toPrecision(4)}`,
- padding: 6,
- align: 'left',
- anchor: 'center',
- clip: true
- },
- legend: { display: false },
- tooltip: { enable: true }
- }
-}
-
-const GetLimitShape = (flowChartData, points, accessor) => {
- const min = [], max = []
-
- for (let point of points) {
- const program = flowChartData.find(v => v.depthStart < point.depth && point.depth < v.depthEnd)
- if (!program) continue
-
- min.push({ x: program[`${accessor}Min`], y: new Date(point.y), label: point.label })
- max.push({ x: program[`${accessor}Max`], y: new Date(point.y), label: point.label })
- }
-
- return min.concat(max.reverse()) ?? []
-}
-
-const GetRandomColor = () => '#' + Math.floor(Math.random()*16777215).toString(16)
-const GetOrCreateDatasetByLineConfig = (data, lineConfig) => {
- let dataset = data?.datasets.find(d => d.label === lineConfig.label)
- if(!dataset) {
- let color = lineConfig.borderColor
- ?? lineConfig.backgroundColor
- ?? lineConfig.color
- ?? GetRandomColor()
-
- dataset = {
- label: lineConfig.label,
- data: [],
- backgroundColor: lineConfig.backgroundColor ?? color,
- borderColor: lineConfig.borderColor ?? color,
- borderWidth: lineConfig.borderWidth ?? 1,
- borderDash: lineConfig.dash ?? [],
- showLine: lineConfig.showLine ?? !lineConfig.isShape,
- fill: lineConfig.fill ?? (lineConfig.isShape ? 'shape' : 'none')
- }
- data.datasets.push(dataset);
- }
- return dataset
-}
-
-export const Column = ({ lineGroup, data, flowChartData, interval, showBorder, style, headerHeight, yDisplay, yStart, showLastValues, pointCount }) => {
- const [dataParams, setDataParams] = useState({data: {datasets:[]}, yStart: new Date(), })
-
- let dataLast = data?.[data.length - 1]
- let pv = lineGroup.filter(line => line.showLabels).map(line => ({
- color: line.color,
- label: line.label,
- unit: line.units,
- value: dataLast?.[line.xAccessorName]
- }))
-
- useEffect(()=>{
- if((lineGroup.length === 0) || (data.length === 0)) return
-
- setDataParams((preDataParams) => {
- lineGroup.forEach(lineCfg => {
- if (lineCfg.isShape) return
- const dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg)
- const points = data.map(dataItem => ({
- x: lineCfg.xConstValue ?? dataItem[lineCfg.xAccessorName],
- label: dataItem[lineCfg.xAccessorName],
- y: new Date(dataItem[lineCfg.yAccessorName]),
- depth: dataItem.wellDepth
- })).filter(point => (point.x ?? null) !== null && (point.y ?? null) !== null)
-
- const lineData = [ ...dataset.data, ...points,]
- if(points?.length > 2)
- lineData.sort((a,b) => a.y > b.y ? 1 : -1)
- if(lineData.length > pointCount)
- lineData.splice(0, (pointCount - lineData.length))
-
- dataset.data = lineData
-
- //Area
- if (flowChartData) {
- lineGroup.filter(cfg => cfg.isShape && cfg.xAccessorName === lineCfg.xAccessorName).forEach(areaCfg => {
- const dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, areaCfg)
- dataset.data = GetLimitShape(flowChartData, lineData, areaCfg.xAccessorName)
- })
- }
- })
-
- preDataParams.yStart = yStart ?? new Date(Math.max(new Date(dataLast.date), preDataParams.yStart ?? new Date(0)))
- if (!yStart)
- preDataParams.yStart.setSeconds(preDataParams.yStart.getSeconds() - interval * 0.97)
- preDataParams.yInterval = interval
- preDataParams.displayLabels = yDisplay ?? false
- return {...preDataParams}
- })
-
- }, [data, lineGroup, interval, yDisplay, yStart, flowChartData, dataLast, pointCount])
-
- return (
-
-
- {pv?.map((v, idx) => (
- {v.label}
- ))}
-
-
- {showLastValues && (
-
- {pv?.map((v, idx) => (
- {v.value?.toFixed(2) ?? '--'} {v.unit}
- ))}
-
- )}
-
-
- {showLastValues && (
-
- )}
-
- )
-}
-
-Column.defaultProps = {
- pointCount: 2048
-}
diff --git a/src/pages/TelemetryView/MonitoringColumn.jsx b/src/pages/TelemetryView/MonitoringColumn.jsx
new file mode 100644
index 0000000..2d7aa5d
--- /dev/null
+++ b/src/pages/TelemetryView/MonitoringColumn.jsx
@@ -0,0 +1,80 @@
+import { Grid, GridItem } from '../../components/Grid'
+import { Column, GetOrCreateDatasetByLineConfig } from '../../components/charts/Column'
+import { ChartTimeOnlineFooter } from './ChartTimeOnlineFooter'
+import { useEffect, useState } from 'react'
+
+const stroke = (sz = '2px', c = 'white') => ({ textShadow: `-${sz} -${sz} 0 ${c}, ${sz} -${sz} 0 ${c}, -${sz} ${sz} 0 ${c}, ${sz} ${sz} 0 ${c}` })
+
+const GetLimitShape = (flowChartData, points, accessor) => {
+ const min = [], max = []
+
+ for (let point of points) {
+ const program = flowChartData.find(v => v.depthStart < point.depth && point.depth < v.depthEnd)
+ if (!program) continue
+
+ min.push({ x: program[`${accessor}Min`], y: new Date(point.y), label: point.label })
+ max.push({ x: program[`${accessor}Max`], y: new Date(point.y), label: point.label })
+ }
+
+ return min.concat(max.reverse()) ?? []
+}
+
+export const MonitoringColumn = ({ lineGroup, data, flowChartData, interval, showBorder, style, headerHeight }) => {
+ const [lineGroupWithoutShapes, setLineGroupWithoutShapes] = useState([])
+ const dataLast = data?.[data.length - 1]
+ const yStart = new Date(+(dataLast?.date ? dataLast.date : new Date()) - interval)
+ const pv = lineGroup.filter(line => line.showLabels).map(line => ({
+ color: line.color,
+ label: line.label,
+ unit: line.units,
+ value: dataLast?.[line.xAccessorName]
+ }))
+
+ const postParsing = (data) => {
+ if (flowChartData) {
+ lineGroupWithoutShapes.forEach(lineCfg => {
+ const lineDataSet = GetOrCreateDatasetByLineConfig(data.data, lineCfg)
+
+ lineGroup.filter(cfg => cfg.isShape && cfg.xAccessorName === lineCfg.xAccessorName).forEach(areaCfg => {
+ const dataset = GetOrCreateDatasetByLineConfig(data.data, areaCfg)
+ dataset.data = GetLimitShape(flowChartData, lineDataSet.data, areaCfg.xAccessorName)
+ })
+ })
+ }
+ }
+
+ useEffect(() => {
+ setLineGroupWithoutShapes(lineGroup.filter(cfg => !cfg.isShape))
+ }, [lineGroup])
+
+ return (
+
+
+ {pv?.map((v, idx) => (
+ {v.label}
+ ))}
+
+
+
+ {pv?.map((v, idx) => (
+ {v.value?.toFixed(2) ?? '--'} {v.unit}
+ ))}
+
+
+
+
+
+ )
+}
+
+MonitoringColumn.defaultProps = {
+ pointCount: 2048
+}
diff --git a/src/pages/TelemetryView/index.jsx b/src/pages/TelemetryView/index.jsx
index 80cb9e7..8f81e94 100644
--- a/src/pages/TelemetryView/index.jsx
+++ b/src/pages/TelemetryView/index.jsx
@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react'
import { Select } from 'antd'
-import { Column } from './Column'
+import { MonitoringColumn } from './MonitoringColumn'
import { CustomColumn } from './CustomColumn'
import ActiveMessagesOnline from './ActiveMessagesOnline'
import { ModeDisplay } from './ModeDisplay'
@@ -22,6 +22,7 @@ import MomentStabPicEnabled from '../../images/DempherOn.png'
import MomentStabPicDisabled from '../../images/DempherOff.png'
import SpinPicEnabled from '../../images/SpinEnabled.png'
import SpinPicDisabled from '../../images/SpinDisabled.png'
+import { PeriodPicker, defaultPeriod } from '../../components/PeriodPicker'
import '../../styles/message.css'
@@ -259,19 +260,6 @@ export const paramsGroups = [
rotorTorqueGroup
]
-const timePeriodCollection = [
- { value: '60', label: '1 минута' },
- { value: '300', label: '5 минут' },
- { value: '600', label: '10 минут' },
- { value: '1800', label: '30 минут' },
- { value: '3600', label: '1 час' },
- { value: '21600', label: '6 часов' },
- { value: '43200', label: '12 часов' },
- { value: '86400', label: '24 часа' }
-]
-
-const defaultChartInterval = '600'
-
const getLast = (data) =>
Array.isArray(data) ? data.slice(-1)[0] : data
@@ -305,13 +293,11 @@ const getIndexOfDrillingBy = (dataSaub) => {
export default function TelemetryView({ idWell }) {
const [dataSaub, setDataSaub] = useState([])
const [dataSpin, setDataSpin] = useState([])
- const [chartInterval, setChartInterval] = useState(defaultChartInterval)
+ const [chartInterval, setChartInterval] = useState(defaultPeriod)
const [wellData, setWellData] = useState({ idState: 0 })
const [showLoader, setShowLoader] = useState(false)
const [flowChartData, setFlowChartData] = useState([])
- const options = timePeriodCollection.map((line) => )
-
const handleDataSaub = (data) => {
if (data) {
data.forEach((_, idx) => {
@@ -382,9 +368,7 @@ export default function TelemetryView({ idWell }) {
Интервал:
-
+
Статус:
@@ -406,7 +390,7 @@ export default function TelemetryView({ idWell }) {
{paramsGroups.map((group, index) =>
-
+ showBorder={getIndexOfDrillingBy(dataSaub) === index}
+ />
)}