forked from ddrilling/asb_cloud_front
* Колонка разделена на общий компонент и частные для мониторинга и архива
* Выбор интервала/периода вынесен в компонент * Оптимизирован ChartTimeBase * Archive разделён на отображение и обработку, добавлен скорлл и подгрузка
This commit is contained in:
parent
565bc1e1e7
commit
274c56dcf7
31
src/components/PeriodPicker.tsx
Normal file
31
src/components/PeriodPicker.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { Select } from 'antd'
|
||||
|
||||
export const defaultPeriod = '600'
|
||||
|
||||
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 часа' }
|
||||
]
|
||||
|
||||
interface PeriodPickerProps {
|
||||
defaultValue?: string
|
||||
onChange?: (value: string, option: any) => void
|
||||
}
|
||||
|
||||
export const PeriodPicker = ({ defaultValue = defaultPeriod, onChange }: PeriodPickerProps) => (
|
||||
<Select defaultValue={defaultValue} onChange={onChange}>
|
||||
{timePeriodCollection.map(period => (
|
||||
<Select.Option key={period.value} value={period.value}>
|
||||
{period.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
)
|
||||
|
||||
export default PeriodPicker
|
@ -71,6 +71,7 @@ const defaultOptions = {
|
||||
position:'top'
|
||||
}
|
||||
},
|
||||
parsing: false,
|
||||
elements:{
|
||||
point:{
|
||||
radius:0,
|
||||
@ -108,7 +109,6 @@ export type ChartTimeData = ChartData<keyof ChartTypeRegistry, {
|
||||
export type ChartTimeDataParams = {
|
||||
data: ChartTimeData,
|
||||
yStart?: Date,
|
||||
yEnd?: Date,
|
||||
yInterval?: number,
|
||||
displayLabels?: Boolean,
|
||||
}
|
||||
@ -197,8 +197,7 @@ export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams
|
||||
const chartRef = useRef<HTMLCanvasElement>(null)
|
||||
const [chart, setChart] = useState<any>()
|
||||
|
||||
useEffect(()=>{
|
||||
if((chartRef.current)&&(!chart)){
|
||||
useEffect(() => {
|
||||
let thisOptions = {}
|
||||
Object.assign(thisOptions, defaultOptions, options)
|
||||
|
||||
@ -206,22 +205,19 @@ export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams
|
||||
type: 'line',
|
||||
plugins: [ChartDataLabels],
|
||||
options: thisOptions,
|
||||
data: dataParams.data
|
||||
data: { datasets: [] }
|
||||
})
|
||||
setChart(newChart)
|
||||
return () => newChart?.destroy()
|
||||
}, [options])
|
||||
|
||||
return () => chart?.destroy()
|
||||
}
|
||||
|
||||
if(!chart) return
|
||||
|
||||
useEffect(() => {
|
||||
if (!chart) return;
|
||||
chart.data = dataParams.data
|
||||
chart.options.aspectRatio = options?.aspectRatio
|
||||
if(dataParams.yStart){
|
||||
const start = new Date(dataParams.yStart)
|
||||
const end = new Date(dataParams.yEnd ?? dataParams.yStart)
|
||||
const interval = Number(dataParams.yInterval ?? (dataParams.yEnd ? end.getSeconds() - start.getSeconds() : 600))
|
||||
if (!dataParams.yEnd)
|
||||
const end = new Date(dataParams.yStart)
|
||||
const interval = Number(dataParams.yInterval ?? 600)
|
||||
end.setSeconds(end.getSeconds() + interval)
|
||||
const { unit, stepSize } = timeParamsByInterval(interval)
|
||||
|
||||
@ -235,7 +231,7 @@ export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams
|
||||
}
|
||||
|
||||
chart.update()
|
||||
}, [chart, dataParams, options])
|
||||
}, [chart, dataParams])
|
||||
|
||||
return(<canvas ref={chartRef} />)
|
||||
}
|
||||
|
91
src/components/charts/Column.jsx
Normal file
91
src/components/charts/Column.jsx
Normal file
@ -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 <ChartTimeBase dataParams = { dataParams } options = { chartPluginsOptions } />
|
||||
}
|
||||
|
||||
Column.defaultProps = {
|
||||
pointCount: 2048,
|
||||
savePreviousData: false
|
||||
}
|
@ -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 (
|
||||
<>
|
||||
<RangePicker
|
||||
showTime
|
||||
allowClear={false}
|
||||
onChange = {onChangeRange}
|
||||
value = {rangeDate}
|
||||
/>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<Grid onWheel={(e) => console.log(e)}>
|
||||
{paramsGroups.map((group, index) => (
|
||||
<GridItem col={index+1} row={'1'} className={'border_small'} key={`${group.label}${index}`} style={{padding:0}}>
|
||||
<Column
|
||||
style={{ width: '15vw' }}
|
||||
data={dataSaub}
|
||||
lineGroup={group}
|
||||
interval={chartInterval}
|
||||
headerHeight={'50px'}
|
||||
/>
|
||||
</GridItem>
|
||||
))}
|
||||
</Grid>
|
||||
</LoaderPortal>
|
||||
</>
|
||||
)
|
||||
}
|
29
src/pages/Archive/ArchiveColumn.jsx
Normal file
29
src/pages/Archive/ArchiveColumn.jsx
Normal file
@ -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 (
|
||||
<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={lineGroup}
|
||||
interval={interval}
|
||||
yDisplay={false}
|
||||
yStart={yStart}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
30
src/pages/Archive/ArchiveDisplay.jsx
Normal file
30
src/pages/Archive/ArchiveDisplay.jsx
Normal file
@ -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 (
|
||||
<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 / 1000}
|
||||
headerHeight={'50px'}
|
||||
yStart={startDate}
|
||||
/>
|
||||
</GridItem>
|
||||
))}
|
||||
</Grid>
|
||||
)
|
||||
}
|
140
src/pages/Archive/index.jsx
Normal file
140
src/pages/Archive/index.jsx
Normal file
@ -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 (
|
||||
<>
|
||||
<Flex style={{margin: '8px 8px 0'}}>
|
||||
<div>
|
||||
Начальная дата:
|
||||
<DatePicker
|
||||
showTime
|
||||
allowClear={false}
|
||||
onChange={(startDate) => setStartDate(new Date(startDate))}
|
||||
value={moment(startDate)}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginLeft: '1rem' }}>
|
||||
Период:
|
||||
<PeriodPicker onChange={(val) => setChartInterval(parseInt(val) * 1000)} />
|
||||
</div>
|
||||
</Flex>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<ArchiveDisplay
|
||||
data={dataSaub}
|
||||
startDate={startDate}
|
||||
interval={chartInterval}
|
||||
onWheel={onGraphWheel}
|
||||
/>
|
||||
</LoaderPortal>
|
||||
</>
|
||||
)
|
||||
}
|
@ -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 (
|
||||
<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' }}>
|
||||
{showLastValues && (
|
||||
<Grid className={'display_chart_values'}>
|
||||
{pv?.map((v, idx) => (
|
||||
<GridItem key={idx} col={1} row={idx} style={{ ...stroke(), color: v.color, padding: '0 4px' }}>{v.value?.toFixed(2) ?? '--'} {v.unit}</GridItem>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
<ChartTimeBase dataParams = {dataParams} options = { chartPluginsOptions } />
|
||||
</div>
|
||||
{showLastValues && (
|
||||
<ChartTimeOnlineFooter data={dataLast} lineGroup={lineGroup} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Column.defaultProps = {
|
||||
pointCount: 2048
|
||||
}
|
80
src/pages/TelemetryView/MonitoringColumn.jsx
Normal file
80
src/pages/TelemetryView/MonitoringColumn.jsx
Normal file
@ -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 (
|
||||
<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'}>
|
||||
{pv?.map((v, idx) => (
|
||||
<GridItem key={idx} col={1} row={idx} style={{ ...stroke(), color: v.color, padding: '0 4px' }}>{v.value?.toFixed(2) ?? '--'} {v.unit}</GridItem>
|
||||
))}
|
||||
</Grid>
|
||||
<Column
|
||||
data={data}
|
||||
lineGroup={lineGroupWithoutShapes}
|
||||
postParsing={postParsing}
|
||||
interval={interval}
|
||||
yDisplay={false}
|
||||
yStart={yStart}
|
||||
savePreviousData={true}
|
||||
/>
|
||||
</div>
|
||||
<ChartTimeOnlineFooter data={dataLast} lineGroup={lineGroup} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
MonitoringColumn.defaultProps = {
|
||||
pointCount: 2048
|
||||
}
|
@ -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) => <Option key={line.value}>{line.label}</Option>)
|
||||
|
||||
const handleDataSaub = (data) => {
|
||||
if (data) {
|
||||
data.forEach((_, idx) => {
|
||||
@ -382,9 +368,7 @@ export default function TelemetryView({ idWell }) {
|
||||
<ModeDisplay data={dataSaub} />
|
||||
<div style={{ marginLeft: '1rem' }}>
|
||||
Интервал:
|
||||
<Select defaultValue={defaultChartInterval} onChange={setChartInterval}>
|
||||
{options}
|
||||
</Select>
|
||||
<PeriodPicker onChange={setChartInterval} />
|
||||
</div>
|
||||
<div style={{ marginLeft: '1rem' }}>
|
||||
Статус:
|
||||
@ -406,7 +390,7 @@ export default function TelemetryView({ idWell }) {
|
||||
</GridItem>
|
||||
{paramsGroups.map((group, index) =>
|
||||
<GridItem col={2 + index} row={'2'} className={'border_small'} key={`${group.label}${index}`} style={{padding:0}}>
|
||||
<Column
|
||||
<MonitoringColumn
|
||||
showLastValues
|
||||
style={{ width: '13vw' }}
|
||||
data={dataSaub}
|
||||
@ -414,7 +398,8 @@ export default function TelemetryView({ idWell }) {
|
||||
lineGroup={group}
|
||||
interval={chartInterval}
|
||||
headerHeight={'50px'}
|
||||
showBorder={getIndexOfDrillingBy(dataSaub) === index} />
|
||||
showBorder={getIndexOfDrillingBy(dataSaub) === index}
|
||||
/>
|
||||
</GridItem>
|
||||
)}
|
||||
<GridItem col={'2'} row={'3'} colSpan={'7'}>
|
||||
|
Loading…
Reference in New Issue
Block a user