forked from ddrilling/asb_cloud_front
Архив и Мониторинг переработаны
This commit is contained in:
parent
9aef0979bd
commit
d5a7d947c6
@ -1,31 +0,0 @@
|
|||||||
import { memo, useMemo } from 'react'
|
|
||||||
|
|
||||||
import { Grid, GridItem } from '@components/Grid'
|
|
||||||
import { Column } from '@components/charts/Column'
|
|
||||||
|
|
||||||
export const ArchiveColumn = memo(({ lineGroup, data, interval, style, headerHeight, yStart }) => {
|
|
||||||
const lgws = useMemo(() => lineGroup.filter(cfg => !cfg.isShape), [lineGroup])
|
|
||||||
const pv = useMemo(() => lgws.filter(line => line.showLabels).map(line => ({
|
|
||||||
color: line.color,
|
|
||||||
label: line.label
|
|
||||||
})), [lgws])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={style}>
|
|
||||||
<Grid style={{ height: headerHeight, margin: 0 }}>
|
|
||||||
{pv?.map((v, idx) => (
|
|
||||||
<GridItem className={'display_chart_label'} key={idx} row={idx} col={1} style={{ color: v.color, padding: '0 8px' }}>{v.label}</GridItem>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
<Column
|
|
||||||
data={data}
|
|
||||||
lineGroup={lgws}
|
|
||||||
interval={interval}
|
|
||||||
yDisplay={false}
|
|
||||||
yStart={yStart}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default ArchiveColumn
|
|
@ -1,66 +0,0 @@
|
|||||||
import { memo, useMemo } from 'react'
|
|
||||||
|
|
||||||
import { Grid, GridItem } from '@components/Grid'
|
|
||||||
|
|
||||||
import { paramsGroups } from '../TelemetryView'
|
|
||||||
import { ArchiveColumn } from './ArchiveColumn'
|
|
||||||
|
|
||||||
const interpolationSearch = (data, begin, end, accessor) => {
|
|
||||||
const fy = (i) => new Date(data[i]?.[accessor] ?? 0)
|
|
||||||
const fx = (y, b, e) => Math.round(b + (y - fy(b)) * (e - b) / (fy(e) - fy(b)))
|
|
||||||
const findIdx = (val, startIdx, c) => {
|
|
||||||
let x = startIdx
|
|
||||||
let endIdx = data.length - 1
|
|
||||||
if(val < fy(startIdx))
|
|
||||||
return startIdx
|
|
||||||
if(val > fy(endIdx))
|
|
||||||
return endIdx
|
|
||||||
for(let i = 0; i < c; i++){
|
|
||||||
x = fx(val, startIdx, endIdx)
|
|
||||||
if(fy(x) < val)
|
|
||||||
startIdx = x
|
|
||||||
else
|
|
||||||
endIdx = x
|
|
||||||
if ((startIdx === endIdx)||(fy(startIdx) === fy(endIdx)))
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
let x0 = findIdx(begin, 0, 5)
|
|
||||||
let x1 = findIdx(end, x0, 3)
|
|
||||||
return { start: x0, end: x1, count: x1 - x0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const cutData = (data, beginDate, endDate) => {
|
|
||||||
if (data?.length > 0) {
|
|
||||||
let { start, end } = interpolationSearch(data, beginDate, endDate, 'date')
|
|
||||||
if (start > 0) start--
|
|
||||||
if (end + 1 < end.length) end++
|
|
||||||
return data.slice(start, end)
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ArchiveDisplay = memo(({data, startDate, interval, onWheel}) => {
|
|
||||||
const endDate = useMemo(() => new Date(+startDate + interval), [startDate, interval])
|
|
||||||
const chartData = useMemo(() => cutData(data, startDate, endDate), [data, startDate, endDate])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Grid onWheel={onWheel}>
|
|
||||||
{paramsGroups.map((group, index) => (
|
|
||||||
<GridItem col={index + 1} row={'1'} className={'border_small'} key={`${group.label}${index}`} style={{ padding: 0 }}>
|
|
||||||
<ArchiveColumn
|
|
||||||
style={{ width: '15vw' }}
|
|
||||||
data={chartData}
|
|
||||||
lineGroup={group}
|
|
||||||
interval={interval}
|
|
||||||
headerHeight={'50px'}
|
|
||||||
yStart={startDate}
|
|
||||||
/>
|
|
||||||
</GridItem>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default ArchiveDisplay
|
|
@ -1,24 +1,30 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { useState, useEffect, memo, useCallback } from 'react'
|
import { useState, useEffect, memo, useCallback, useMemo } from 'react'
|
||||||
import { useSearchParams } from 'react-router-dom'
|
import { useSearchParams } from 'react-router-dom'
|
||||||
|
|
||||||
import { useIdWell } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import { Flex } from '@components/Grid'
|
import { Flex } from '@components/Grid'
|
||||||
|
import { D3MonitoringCharts } from '@components/d3'
|
||||||
import { CopyUrlButton } from '@components/CopyUrl'
|
import { CopyUrlButton } from '@components/CopyUrl'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { DatePickerWrapper, makeDateSorter } from '@components/Table'
|
import { DatePickerWrapper, makeDateSorter } from '@components/Table'
|
||||||
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
||||||
import { range, wrapPrivateComponent } from '@utils'
|
import { formatDate, range, wrapPrivateComponent } from '@utils'
|
||||||
import { TelemetryDataSaubService } from '@api'
|
import { TelemetryDataSaubService } from '@api'
|
||||||
|
|
||||||
import { normalizeData } from '../TelemetryView'
|
import { chartGroups, normalizeData } from '../TelemetryView'
|
||||||
import { ArchiveDisplay, cutData } from './ArchiveDisplay'
|
import { Select } from 'antd'
|
||||||
|
|
||||||
const DATA_COUNT = 2048 // Колличество точек на подгрузку графика
|
const DATA_COUNT = 2048 // Колличество точек на подгрузку графика
|
||||||
const ADDITIVE_PAGES = 2 // Дополнительные данные для графиков
|
const ADDITIVE_PAGES = 2 // Дополнительные данные для графиков
|
||||||
const LOADING_TRIGGER = 0.5
|
const LOADING_TRIGGER = 0.5
|
||||||
const WHEEL_SENSITIVITY = 1 / 530
|
|
||||||
|
const scrollOptions = [
|
||||||
|
{ label: '10%', value: 0.1 },
|
||||||
|
{ label: '15%', value: 0.15 },
|
||||||
|
{ label: '25%', value: 0.25 },
|
||||||
|
]
|
||||||
|
|
||||||
const getLoadingInterval = (loaded, startDate, interval) => {
|
const getLoadingInterval = (loaded, startDate, interval) => {
|
||||||
// Если данные загружены и дата не заходит за тригер дозагрузка не требуется
|
// Если данные загружены и дата не заходит за тригер дозагрузка не требуется
|
||||||
@ -58,6 +64,42 @@ const getLoadingInterval = (loaded, startDate, interval) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const interpolationSearch = (data, begin, end, accessor) => {
|
||||||
|
const fy = (i) => new Date(data[i]?.[accessor] ?? 0)
|
||||||
|
const fx = (y, b, e) => Math.round(b + (y - fy(b)) * (e - b) / (fy(e) - fy(b)))
|
||||||
|
const findIdx = (val, startIdx, c) => {
|
||||||
|
let x = startIdx
|
||||||
|
let endIdx = data.length - 1
|
||||||
|
if(val < fy(startIdx))
|
||||||
|
return startIdx
|
||||||
|
if(val > fy(endIdx))
|
||||||
|
return endIdx
|
||||||
|
for(let i = 0; i < c; i++){
|
||||||
|
x = fx(val, startIdx, endIdx)
|
||||||
|
if(fy(x) < val)
|
||||||
|
startIdx = x
|
||||||
|
else
|
||||||
|
endIdx = x
|
||||||
|
if ((startIdx === endIdx)||(fy(startIdx) === fy(endIdx)))
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
let x0 = findIdx(begin, 0, 5)
|
||||||
|
let x1 = findIdx(end, x0, 3)
|
||||||
|
return { start: x0, end: x1, count: x1 - x0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cutData = (data, beginDate, endDate) => {
|
||||||
|
if (data?.length > 0) {
|
||||||
|
let { start, end } = interpolationSearch(data, beginDate, endDate, 'date')
|
||||||
|
if (start > 0) start--
|
||||||
|
if (end + 1 < end.length) end++
|
||||||
|
return data.slice(start, end)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
const Archive = memo(() => {
|
const Archive = memo(() => {
|
||||||
const [dataSaub, setDataSaub] = useState([])
|
const [dataSaub, setDataSaub] = useState([])
|
||||||
const [dateLimit, setDateLimit] = useState({ from: 0, to: new Date() })
|
const [dateLimit, setDateLimit] = useState({ from: 0, to: new Date() })
|
||||||
@ -70,20 +112,21 @@ const Archive = memo(() => {
|
|||||||
const getInitialRange = useCallback(() => parseInt(search.get('range') ?? defaultPeriod) * 1000, [search])
|
const getInitialRange = useCallback(() => parseInt(search.get('range') ?? defaultPeriod) * 1000, [search])
|
||||||
const getInitialDate = useCallback(() => new Date(search.get('start') ?? (Date.now() - chartInterval)), [search])
|
const getInitialDate = useCallback(() => new Date(search.get('start') ?? (Date.now() - chartInterval)), [search])
|
||||||
|
|
||||||
|
const [scrollPercent, setScrollPercent] = useState(0.15)
|
||||||
const [chartInterval, setChartInterval] = useState(getInitialRange)
|
const [chartInterval, setChartInterval] = useState(getInitialRange)
|
||||||
const [startDate, setStartDate] = useState(getInitialDate)
|
const [startDate, setStartDate] = useState(getInitialDate)
|
||||||
|
|
||||||
const onGraphWheel = useCallback((e) => {
|
const onGraphWheel = useCallback((e) => {
|
||||||
if (loaded && dateLimit.from && dateLimit.to) {
|
if (loaded && dateLimit.from && dateLimit.to) {
|
||||||
setStartDate((prevStartDate) => {
|
setStartDate((prevStartDate) => {
|
||||||
const offset = e.deltaY * chartInterval * WHEEL_SENSITIVITY
|
const offset = e.deltaY / 100 * chartInterval * scrollPercent
|
||||||
const nextStartDate = +prevStartDate + offset
|
const nextStartDate = +prevStartDate + offset
|
||||||
const firstPossibleDate = Math.max(loaded.start, dateLimit.from)
|
const firstPossibleDate = Math.max(loaded.start, dateLimit.from)
|
||||||
const lastPossibleDate = Math.min(dateLimit.to, (loaded.end ?? Date.now())) - chartInterval
|
const lastPossibleDate = Math.min(dateLimit.to, (loaded.end ?? Date.now())) - chartInterval
|
||||||
return new Date(Math.max(firstPossibleDate, Math.min(nextStartDate, lastPossibleDate)))
|
return new Date(Math.max(firstPossibleDate, Math.min(nextStartDate, lastPossibleDate)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [loaded, dateLimit, chartInterval])
|
}, [loaded, dateLimit, chartInterval, scrollPercent])
|
||||||
|
|
||||||
const isDateDisabled = useCallback((date) => {
|
const isDateDisabled = useCallback((date) => {
|
||||||
if (!date) return false
|
if (!date) return false
|
||||||
@ -171,6 +214,9 @@ const Archive = memo(() => {
|
|||||||
setDataSaub([])
|
setDataSaub([])
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const domain = useMemo(() => ({ min: startDate, max: new Date(+startDate + chartInterval)}), [startDate, chartInterval])
|
||||||
|
const chartData = useMemo(() => cutData(dataSaub, domain.min, domain.max), [dataSaub, domain])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex style={{margin: '8px 8px 0'}}>
|
<Flex style={{margin: '8px 8px 0'}}>
|
||||||
@ -187,13 +233,23 @@ const Archive = memo(() => {
|
|||||||
Период:
|
Период:
|
||||||
<PeriodPicker value={chartInterval / 1000} onChange={onRangeChange} />
|
<PeriodPicker value={chartInterval / 1000} onChange={onRangeChange} />
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ marginLeft: '1rem' }}>
|
||||||
|
Прокрутка:
|
||||||
|
<Select options={scrollOptions} value={scrollPercent} onChange={setScrollPercent} />
|
||||||
|
</div>
|
||||||
<CopyUrlButton style={{ marginLeft: '1rem' }} />
|
<CopyUrlButton style={{ marginLeft: '1rem' }} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
<ArchiveDisplay
|
<D3MonitoringCharts
|
||||||
data={dataSaub}
|
datasetGroups={chartGroups}
|
||||||
startDate={startDate}
|
data={chartData}
|
||||||
interval={chartInterval}
|
yDomain={domain}
|
||||||
|
yTicks={{
|
||||||
|
visible: true,
|
||||||
|
format: (d) => formatDate(d)
|
||||||
|
}}
|
||||||
|
plugins={{ menu: { enabled: false }}}
|
||||||
|
height={'76vh'}
|
||||||
onWheel={onGraphWheel}
|
onWheel={onGraphWheel}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</LoaderPortal>
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
import { memo } from 'react'
|
|
||||||
import { Popover } from 'antd'
|
|
||||||
import { ControlOutlined } from '@ant-design/icons'
|
|
||||||
|
|
||||||
import { ValueDisplay } from '@components/Display'
|
|
||||||
|
|
||||||
export const ChartTimeOnlineFooter = memo(({ data, lineGroup }) => {
|
|
||||||
const getFooterData = (name) => {
|
|
||||||
const dataIdx = data && lineGroup?.find(line => line?.footer === name)?.xAccessorName
|
|
||||||
return (<ValueDisplay value={data?.[dataIdx]}/>)
|
|
||||||
}
|
|
||||||
|
|
||||||
const spValues = getFooterData('SP')
|
|
||||||
const idleValues = getFooterData('IDLE')
|
|
||||||
|
|
||||||
const popContent = lineGroup?.filter(line => line.footer === true).map(line => (
|
|
||||||
<div key={line.label}>
|
|
||||||
{line.label}
|
|
||||||
<ValueDisplay value={data?.[line.xAccessorName]}/>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
|
|
||||||
return(
|
|
||||||
<div>
|
|
||||||
{popContent?.length > 0 ? (
|
|
||||||
<Popover content={popContent}>
|
|
||||||
<div className={'chart-footer'}>
|
|
||||||
<ControlOutlined className={'display_label'}/>
|
|
||||||
<div style={{display: 'flex', flexDirection: 'column'}}>{spValues}</div>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
) : (
|
|
||||||
<div style={{display: 'flex', justifyContent: 'flex-end'}}>
|
|
||||||
<div style={{display: 'flex', flexDirection: 'column'}}>{spValues}</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div style={{display: 'flex'}}>
|
|
||||||
<span className='display_label'>х.х.</span>
|
|
||||||
<div style={{display: 'flex', flexDirection: 'column'}}>{idleValues}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default ChartTimeOnlineFooter
|
|
@ -1,123 +0,0 @@
|
|||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
|
||||||
|
|
||||||
import { Grid, GridItem } from '@components/Grid'
|
|
||||||
import { makeDateSorter } from '@components/Table'
|
|
||||||
import { Column, GetOrCreateDatasetByLineConfig } from '@components/charts/Column'
|
|
||||||
|
|
||||||
import { ChartTimeOnlineFooter } from './ChartTimeOnlineFooter'
|
|
||||||
|
|
||||||
const GetLimitShape = (flowChartData, points, accessor) => {
|
|
||||||
const min = [], max = []
|
|
||||||
|
|
||||||
for (const 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 RemoveSimilar = (input, accessor) => {
|
|
||||||
if (!input || input.length <= 0) return input
|
|
||||||
const data = [input[0]]
|
|
||||||
for (let i = 1; i < input.length; i++)
|
|
||||||
if (input[i][accessor] !== input[i - 1][accessor])
|
|
||||||
data.push(input[i])
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
const addPointData = (point) => ({ depth: point.wellDepth })
|
|
||||||
|
|
||||||
const ChartValues = memo(({ pv }) => pv?.map((v, idx) => {
|
|
||||||
const text = `${v.value?.toFixed(2) ?? '--'} ${v.unit}`
|
|
||||||
return (
|
|
||||||
<GridItem
|
|
||||||
key={idx}
|
|
||||||
row={idx + 1}
|
|
||||||
col={1}
|
|
||||||
className={'monitoring_value'}
|
|
||||||
style={{ color: v.color, padding: '0 4px' }}
|
|
||||||
data-before={text}
|
|
||||||
children={text}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const MonitoringColumn = memo(({ lineGroup, data, flowChartData, interval, showBorder, style, headerHeight, pointCount = 2048, additionalLabels }) => {
|
|
||||||
const [dataStore, setDataStore] = useState([])
|
|
||||||
const dataLast = useMemo(() => data?.[data.length - 1], [data])
|
|
||||||
const yStart = useMemo(() => new Date((dataLast?.date ? +new Date(dataLast.date) : Date.now()) - interval * 0.97), [dataLast, interval])
|
|
||||||
const pv = useMemo(() => lineGroup.filter(line => line.showLabels).map(line => ({
|
|
||||||
color: line.color,
|
|
||||||
label: line.label,
|
|
||||||
unit: line.units,
|
|
||||||
value: dataLast?.[line.xAccessorName]
|
|
||||||
})), [lineGroup, dataLast])
|
|
||||||
|
|
||||||
const lineGroupWithoutShapes = useMemo(() => lineGroup.filter(cfg => !cfg.isShape), [lineGroup])
|
|
||||||
|
|
||||||
const postParsing = useCallback((data) => {
|
|
||||||
lineGroupWithoutShapes.forEach(lineCfg => {
|
|
||||||
const lineDataSet = GetOrCreateDatasetByLineConfig(data.data, lineCfg)
|
|
||||||
|
|
||||||
if (flowChartData) {
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [lineGroupWithoutShapes, flowChartData, lineGroup])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setDataStore(prevData => {
|
|
||||||
let newData = [...prevData, ...data]
|
|
||||||
newData.sort(makeDateSorter('date'))
|
|
||||||
newData = RemoveSimilar(newData, 'date')
|
|
||||||
if (newData.length > pointCount)
|
|
||||||
newData.splice(0, pointCount - newData.length)
|
|
||||||
return newData
|
|
||||||
})
|
|
||||||
}, [data, pointCount])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={style}>
|
|
||||||
<Grid style={{ height: headerHeight, boxShadow: showBorder ? 'inset 0px 0px 0px 3px black' : '', margin: 0 }}>
|
|
||||||
{pv?.map((v, idx) => (
|
|
||||||
<GridItem className={'display_chart_label'} key={idx} row={idx} col={1} style={{ color: v.color, padding: '0 8px' }}>{v.label}</GridItem>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
<div style={{ position: 'relative' }}>
|
|
||||||
<Grid className={'display_chart_values'}>
|
|
||||||
<ChartValues pv={pv} />
|
|
||||||
{additionalLabels?.map((label, idx) => (
|
|
||||||
<GridItem
|
|
||||||
key={idx}
|
|
||||||
row={(pv?.length ?? 0) + idx + 1}
|
|
||||||
col={1}
|
|
||||||
className={'monitoring_value'}
|
|
||||||
style={{ color: 'black', padding: '0 4px' }}
|
|
||||||
data-before={label}
|
|
||||||
children={label}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
<Column
|
|
||||||
data={dataStore}
|
|
||||||
lineGroup={lineGroupWithoutShapes}
|
|
||||||
postParsing={postParsing}
|
|
||||||
additionalPointData={addPointData}
|
|
||||||
interval={interval}
|
|
||||||
yDisplay={false}
|
|
||||||
yStart={yStart}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<ChartTimeOnlineFooter data={dataLast} lineGroup={lineGroup} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default MonitoringColumn
|
|
@ -1,13 +1,14 @@
|
|||||||
import { Select } from 'antd'
|
import { Select } from 'antd'
|
||||||
import { useState, useEffect, useCallback, memo } from 'react'
|
import { useState, useEffect, useCallback, memo, useMemo } from 'react'
|
||||||
|
|
||||||
import { useIdWell } from '@asb/context'
|
import { useIdWell } from '@asb/context'
|
||||||
import { makeDateSorter } from '@components/Table'
|
import { makeDateSorter } from '@components/Table'
|
||||||
|
import { D3MonitoringCharts } from '@components/d3'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { Grid, GridItem, Flex } from '@components/Grid'
|
import { Grid, GridItem, Flex } from '@components/Grid'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
||||||
import { hasPermission, wrapPrivateComponent } from '@utils'
|
import { formatDate, hasPermission, wrapPrivateComponent } from '@utils'
|
||||||
import { Subscribe } from '@services/signalr'
|
import { Subscribe } from '@services/signalr'
|
||||||
import {
|
import {
|
||||||
DrillFlowChartService,
|
DrillFlowChartService,
|
||||||
@ -17,7 +18,6 @@ import {
|
|||||||
WellService
|
WellService
|
||||||
} from '@api'
|
} from '@api'
|
||||||
|
|
||||||
import { MonitoringColumn } from './MonitoringColumn'
|
|
||||||
import { CustomColumn } from './CustomColumn'
|
import { CustomColumn } from './CustomColumn'
|
||||||
import ActiveMessagesOnline from './ActiveMessagesOnline'
|
import ActiveMessagesOnline from './ActiveMessagesOnline'
|
||||||
import { ModeDisplay } from './ModeDisplay'
|
import { ModeDisplay } from './ModeDisplay'
|
||||||
@ -34,241 +34,61 @@ import '@styles/message.css'
|
|||||||
|
|
||||||
const { Option } = Select
|
const { Option } = Select
|
||||||
|
|
||||||
|
const yAxis = {
|
||||||
|
type: 'time',
|
||||||
|
accessor: (d) => new Date(d.date),
|
||||||
|
format: (d) => formatDate(d),
|
||||||
|
}
|
||||||
|
|
||||||
const dash = [7, 3]
|
const dash = [7, 3]
|
||||||
|
|
||||||
const blockHeightGroup = [
|
const makeDataset = (label, color, key, unit, other) => ({
|
||||||
{
|
key,
|
||||||
label: 'Высота блока',
|
label,
|
||||||
units: 'м',
|
color,
|
||||||
xAccessorName: 'blockPosition',
|
yAxis,
|
||||||
yAccessorName: 'date',
|
xAxis: {
|
||||||
color: '#333',
|
type: 'linear',
|
||||||
showLabels: true
|
accessor: key,
|
||||||
}, {
|
unit,
|
||||||
label: 'Глубина скважины',
|
},
|
||||||
units: 'м',
|
type: 'line',
|
||||||
xAccessorName: 'wellDepth',
|
...other,
|
||||||
yAccessorName: 'date',
|
})
|
||||||
color: '#333',
|
|
||||||
showLine: false,
|
|
||||||
showDatalabels: true,
|
|
||||||
xConstValue: 50,
|
|
||||||
dash
|
|
||||||
}, {
|
|
||||||
label: 'Расход',
|
|
||||||
units: 'л/c',
|
|
||||||
xAccessorName: 'flow',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#077',
|
|
||||||
showLabels: true,
|
|
||||||
showLine: true
|
|
||||||
}, {
|
|
||||||
label: 'Предел расхода',
|
|
||||||
units: 'л/с',
|
|
||||||
xAccessorName: 'flow',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: 'rgba(0,119,119,.1)',
|
|
||||||
isShape: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const blockSpeedGroup = [
|
export const chartGroups = [
|
||||||
{
|
[
|
||||||
label: 'Скорость блока',
|
makeDataset('Высота блока', '#333', 'blockPosition', 'м'),
|
||||||
units: 'м/ч',
|
makeDataset('Глубина скважины', '#333', 'wellDepth', 'м', { dash, yDomain: { min: 0, max: 5000, hideXAxis: true } }),
|
||||||
xAccessorName: 'blockSpeed',
|
makeDataset('Расход', '#077', 'flow', 'л/с'),
|
||||||
yAccessorName: 'date',
|
], [
|
||||||
color: '#0a0',
|
makeDataset('Скорость блока', '#0a0', 'blockSpeed', 'м/ч'),
|
||||||
showLabels: true,
|
makeDataset('Скорость заданная', '#0a0', 'blockSpeedSp', 'м/ч', { dash, yDomain: { min: 0, max: 50 } }),
|
||||||
}, {
|
], [
|
||||||
label: 'Скорость заданная',
|
makeDataset('Давление', '#c00', 'pressure', 'атм', { yDomain: { min: 50, max: 150 } }),
|
||||||
units: 'м/ч',
|
makeDataset('Давление заданное', '#c00', 'pressureSp', 'атм', { yDomain: { min: 50, max: 150 } }),
|
||||||
xAccessorName: 'blockSpeedSp',
|
makeDataset('Давление ХХ', '#c00', 'pressureIdle', 'атм', { dash, yDomain: { min: 50, max: 150 } }),
|
||||||
yAccessorName: 'date',
|
makeDataset('Перепад давления максимальный', '#c00', 'pressureDeltaLimitMax', 'атм', { dash, yDomain: { min: 50, max: 150 } }),
|
||||||
color: '#0a0',
|
], [
|
||||||
footer: 'SP',
|
makeDataset('Осевая нагрузка', '#00a', 'axialLoad', 'т', { yDomain: { min: 0, max: 15 } }),
|
||||||
dash
|
makeDataset('Осевая нагрузка заданная', '#00a', 'axialLoadSp', 'т', { dash, yDomain: { min: 0, max: 15 } }),
|
||||||
}
|
makeDataset('Осевая нагрузка максимальная', '#00a', 'axialLoadLimitMax', 'т', { dash, yDomain: { min: 0, max: 15 } }),
|
||||||
]
|
], [
|
||||||
|
makeDataset('Вес на крюке', '#0aa', 'hookWeight', 'т', { yDomain: { min: 0, max: 80 } }),
|
||||||
const pressureGroup = [
|
makeDataset('Вес инструмента ХХ', '#0aa', 'hookWeightIdle', 'т', { dash, yDomain: { min: 0, max: 80 } }),
|
||||||
{
|
makeDataset('Вес инструмента минимальный', '#0aa', 'hookWeightLimitMin', 'т', { dash, yDomain: { min: 0, max: 80 } }),
|
||||||
label: 'Давление',
|
makeDataset('Вес инструмента максимальный', '#0aa', 'hookWeightLimitMax', 'т', { dash, yDomain: { min: 0, max: 80 } }),
|
||||||
units: 'атм',
|
makeDataset('Обороты ротора', '#0aa', 'rotorSpeed', 'об/мин', { yDomain: { min: 0, max: 80 } }),
|
||||||
xAccessorName: 'pressure',
|
], [
|
||||||
yAccessorName: 'date',
|
makeDataset('Момент на роторе', '#a0a', 'rotorTorque', 'кН·м', { yDomain: { min: 0, max: 30 } }),
|
||||||
color: '#c00',
|
makeDataset('План. Момент на роторе', '#a0a', 'rotorTorqueSp', 'кН·м', { dash, yDomain: { min: 0, max: 30 } }),
|
||||||
showLabels: true
|
makeDataset('Момент на роторе х.х.', '#a0a', 'rotorTorqueIdle', 'кН·м', { dash, yDomain: { min: 0, max: 30 } }),
|
||||||
}, {
|
makeDataset('Момент максимальный', '#a0a', 'rotorTorqueLimitMax', 'кН·м', { dash, yDomain: { min: 0, max: 30 } }),
|
||||||
label: 'Давление заданное',
|
]
|
||||||
units: 'атм',
|
|
||||||
xAccessorName: 'pressureSp',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#c00',
|
|
||||||
footer: 'SP',
|
|
||||||
dash
|
|
||||||
}, {
|
|
||||||
label: 'Давление ХХ',
|
|
||||||
units: 'атм',
|
|
||||||
xAccessorName: 'pressureIdle',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#c00',
|
|
||||||
footer: 'IDLE',
|
|
||||||
dash
|
|
||||||
}, {
|
|
||||||
label: 'Перепад давления максимальный',
|
|
||||||
units: 'атм',
|
|
||||||
xAccessorName: 'pressureDeltaLimitMax',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#c00',
|
|
||||||
footer: true,
|
|
||||||
dash
|
|
||||||
}, {
|
|
||||||
label: 'Предел давления заданный',
|
|
||||||
units: 'атм',
|
|
||||||
xAccessorName: 'pressure',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: 'rgba(204,0,0,.1)',
|
|
||||||
isShape: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const axialLoadGroup = [
|
|
||||||
{
|
|
||||||
label: 'Осевая нагрузка',
|
|
||||||
units: 'т',
|
|
||||||
xAccessorName: 'axialLoad',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#00a',
|
|
||||||
showLabels: true
|
|
||||||
}, {
|
|
||||||
label: 'Осевая нагрузка заданная',
|
|
||||||
units: 'т',
|
|
||||||
xAccessorName: 'axialLoadSp',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#00a',
|
|
||||||
footer: 'SP',
|
|
||||||
dash
|
|
||||||
}, {
|
|
||||||
label: 'Осевая нагрузка максимальная',
|
|
||||||
units: 'т',
|
|
||||||
xAccessorName: 'axialLoadLimitMax',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#00a',
|
|
||||||
footer: true,
|
|
||||||
dash
|
|
||||||
}, {
|
|
||||||
label: 'Пределы осевой нагрузки',
|
|
||||||
units: 'т',
|
|
||||||
xAccessorName: 'axialLoad',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: 'rgba(0,0,170,.1)',
|
|
||||||
isShape: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const hookWeightGroup = [
|
|
||||||
{
|
|
||||||
label: 'Вес на крюке',
|
|
||||||
units: 'т',
|
|
||||||
xAccessorName: 'hookWeight',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#0aa',
|
|
||||||
showLabels: true
|
|
||||||
}, {
|
|
||||||
label: 'Вес инструмента ХХ',
|
|
||||||
units: 'т',
|
|
||||||
xAccessorName: 'hookWeightIdle',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#0aa',
|
|
||||||
footer: 'IDLE',
|
|
||||||
dash
|
|
||||||
}, {
|
|
||||||
label: 'Вес инструмента минимальный',
|
|
||||||
units: 'т',
|
|
||||||
xAccessorName: 'hookWeightLimitMin',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#0aa',
|
|
||||||
footer: true,
|
|
||||||
dash
|
|
||||||
}, {
|
|
||||||
label: 'Вес инструмента максимальный',
|
|
||||||
units: 'т',
|
|
||||||
xAccessorName: 'hookWeightLimitMax',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#0aa',
|
|
||||||
footer: true,
|
|
||||||
dash
|
|
||||||
}, {
|
|
||||||
label: 'Обороты ротора',
|
|
||||||
units: 'об/мин',
|
|
||||||
xAccessorName: 'rotorSpeed',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#aa0',
|
|
||||||
showLabels: true
|
|
||||||
}, {
|
|
||||||
label: 'Скорость вращения ВСП максимальная',
|
|
||||||
units: 'об/мин',
|
|
||||||
xAccessorName: 'rotorSpeed',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: 'rgba(170,170,0,.1)',
|
|
||||||
isShape: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const rotorTorqueGroup = [
|
|
||||||
{
|
|
||||||
label: 'Момент на роторе',
|
|
||||||
units: 'кН·м',
|
|
||||||
xAccessorName: 'rotorTorque',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#a0a',
|
|
||||||
showLabels: true
|
|
||||||
}, {
|
|
||||||
label: 'План. Момент на роторе',
|
|
||||||
units: 'кН·м',
|
|
||||||
xAccessorName: 'rotorTorqueSp',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#a0a',
|
|
||||||
footer: 'SP',
|
|
||||||
dash
|
|
||||||
}, {
|
|
||||||
label: 'Момент на роторе х.х.',
|
|
||||||
units: 'кН·м',
|
|
||||||
xAccessorName: 'rotorTorqueIdle',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#a0a',
|
|
||||||
footer: 'IDLE',
|
|
||||||
dash
|
|
||||||
}, {
|
|
||||||
label: 'Момент максимальный',
|
|
||||||
units: 'кН·м',
|
|
||||||
xAccessorName: 'rotorTorqueLimitMax',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: '#a0a',
|
|
||||||
footer: true,
|
|
||||||
dash
|
|
||||||
}, {
|
|
||||||
label: 'Ограничения момента',
|
|
||||||
units: 'кН·м',
|
|
||||||
xAccessorName: 'rotorTorque',
|
|
||||||
yAccessorName: 'date',
|
|
||||||
color: 'rgba(170,0,170,.1)',
|
|
||||||
isShape: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
export const paramsGroups = [
|
|
||||||
blockHeightGroup,
|
|
||||||
blockSpeedGroup,
|
|
||||||
pressureGroup,
|
|
||||||
axialLoadGroup,
|
|
||||||
hookWeightGroup,
|
|
||||||
rotorTorqueGroup
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const getLast = (data) =>
|
const getLast = (data) =>
|
||||||
Array.isArray(data) ? data.slice(-1)[0] : data
|
Array.isArray(data) ? data.at(-1) : data
|
||||||
|
|
||||||
const isMseEnabled = (dataSaub) => {
|
const isMseEnabled = (dataSaub) => {
|
||||||
const lastData = getLast(dataSaub)
|
const lastData = getLast(dataSaub)
|
||||||
@ -285,18 +105,6 @@ const isSpinEnabled = (dataSpin) => {
|
|||||||
return lastData?.state > 0 && lastData?.state !== 6
|
return lastData?.state > 0 && lastData?.state !== 6
|
||||||
}
|
}
|
||||||
|
|
||||||
const getIndexOfDrillingBy = (dataSaub) => {
|
|
||||||
const order = {
|
|
||||||
0: -1,
|
|
||||||
1: 1, // скорость
|
|
||||||
2: 2, // давление
|
|
||||||
3: 3, // нагрузка
|
|
||||||
4: 5, // момент
|
|
||||||
}
|
|
||||||
const idFeedRegulator = getLast(dataSaub)?.idFeedRegulator ?? 0
|
|
||||||
return order[idFeedRegulator] ?? -1
|
|
||||||
}
|
|
||||||
|
|
||||||
export const normalizeData = (data) => data?.map(item => ({
|
export const normalizeData = (data) => data?.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
rotorSpeed: item.rotorSpeed < 1 ? 0 : item.rotorSpeed,
|
rotorSpeed: item.rotorSpeed < 1 ? 0 : item.rotorSpeed,
|
||||||
@ -312,6 +120,7 @@ const TelemetryView = memo(() => {
|
|||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [flowChartData, setFlowChartData] = useState([])
|
const [flowChartData, setFlowChartData] = useState([])
|
||||||
const [rop, setRop] = useState(null)
|
const [rop, setRop] = useState(null)
|
||||||
|
const [domain, setDomain] = useState({})
|
||||||
|
|
||||||
const idWell = useIdWell()
|
const idWell = useIdWell()
|
||||||
|
|
||||||
@ -372,12 +181,30 @@ const TelemetryView = memo(() => {
|
|||||||
'Задание состояния скважины'
|
'Задание состояния скважины'
|
||||||
), [idWell, wellData])
|
), [idWell, wellData])
|
||||||
|
|
||||||
const columnAdditionalLabels = {
|
useEffect(() => {
|
||||||
1: rop && [
|
if (dataSaub.length <= 0) return
|
||||||
`ROP сред: ${rop.ropAverage.toFixed(2)} м/ч`,
|
const last = new Date(dataSaub.at(-1).date)
|
||||||
`ROP макс: ${rop.ropMax.toFixed(2)} м/ч`
|
setDomain({
|
||||||
]
|
min: new Date(+last - chartInterval * 1000),
|
||||||
}
|
max: last
|
||||||
|
})
|
||||||
|
}, [dataSaub, chartInterval])
|
||||||
|
|
||||||
|
const filteredData = useMemo(() => {
|
||||||
|
let i, j
|
||||||
|
for (i = 0; i < dataSaub.length; i++) {
|
||||||
|
const date = +new Date(dataSaub[i]?.date)
|
||||||
|
if (date >= +domain.min) break
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = dataSaub.length - 1; j >= i; j--) {
|
||||||
|
const date = +new Date(dataSaub[i]?.date)
|
||||||
|
if (date <= +domain.max) break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i >= j) return []
|
||||||
|
return dataSaub.slice(i, j)
|
||||||
|
}, [dataSaub, domain])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
@ -409,21 +236,19 @@ const TelemetryView = memo(() => {
|
|||||||
<GridItem col={'1'} row={'2'} rowSpan={'3'} style={{ minWidth: '260px', width: '0.142fr' }}>
|
<GridItem col={'1'} row={'2'} rowSpan={'3'} style={{ minWidth: '260px', width: '0.142fr' }}>
|
||||||
<CustomColumn data={dataSaub} />
|
<CustomColumn data={dataSaub} />
|
||||||
</GridItem>
|
</GridItem>
|
||||||
{paramsGroups.map((group, index) =>
|
<GridItem col={2} row={2} colSpan={8} rowSpan={2}>
|
||||||
<GridItem col={2 + index} row={'2'} className={'border_small'} key={`${group.label}${index}`} style={{padding:0}}>
|
<D3MonitoringCharts
|
||||||
<MonitoringColumn
|
datasetGroups={chartGroups}
|
||||||
showLastValues
|
data={filteredData}
|
||||||
style={{ width: '13vw' }}
|
yDomain={domain}
|
||||||
data={dataSaub}
|
yTicks={{
|
||||||
flowChartData={flowChartData}
|
visible: true,
|
||||||
lineGroup={group}
|
format: (d) => formatDate(d)
|
||||||
interval={chartInterval * 1000}
|
}}
|
||||||
headerHeight={'50px'}
|
plugins={{ menu: { enabled: false }}}
|
||||||
showBorder={getIndexOfDrillingBy(dataSaub) === index}
|
height={'70vh'}
|
||||||
additionalLabels={columnAdditionalLabels[index]}
|
/>
|
||||||
/>
|
</GridItem>
|
||||||
</GridItem>
|
|
||||||
)}
|
|
||||||
<GridItem col={'2'} row={'3'} colSpan={'7'}>
|
<GridItem col={'2'} row={'3'} colSpan={'7'}>
|
||||||
<ActiveMessagesOnline idWell={idWell} />
|
<ActiveMessagesOnline idWell={idWell} />
|
||||||
</GridItem>
|
</GridItem>
|
||||||
|
Loading…
Reference in New Issue
Block a user