forked from ddrilling/asb_cloud_front
Merge branch 'feature/CF2-87-Archive-page-redesign' into dev
This commit is contained in:
commit
2663b1f6dd
@ -1,123 +0,0 @@
|
|||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { Button, Select, Tag, Popover, Row, Tooltip } from 'antd';
|
|
||||||
import { ChartTimeArchive } from './charts/ChartTimeArchive';
|
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
const linesCollection = [
|
|
||||||
{ label: "Глубина забоя", xAccessorName: "wellDepth", color: '#f00' },
|
|
||||||
{ label: "Положение инструмента", xAccessorName: "bitDepth", color: '#ff0' },
|
|
||||||
{ label: "Положение талевого блока", xAccessorName: "blockPosition", color: '#f0f' },
|
|
||||||
{ label: "Талевый блок. Мин положение", xAccessorName: "blockPositionMin", color: '#0ff' },
|
|
||||||
{ label: "Талевый блок. Макс положение", xAccessorName: "blockPositionMax", color: '#0f0' },
|
|
||||||
{ label: "Скорость талевого блока", xAccessorName: "blockSpeed", color: '#00f' },
|
|
||||||
|
|
||||||
{ label: "Скорости талевого блока. Задание", xAccessorName: "blockSpeedSp", color: '#c00' },
|
|
||||||
{ label: "Талевый блок. Задание скорости для роторного бурения", xAccessorName: "blockSpeedSpRotor", color: '#cc0' },
|
|
||||||
{ label: "Талевый блок. Задание скорости для режима слайда", xAccessorName: "blockSpeedSpSlide", color: '#c0c' },
|
|
||||||
{ label: "Талевый блок. Задание скорости для проработки", xAccessorName: "blockSpeedSpDevelop", color: '#0cc' },
|
|
||||||
{ label: "Давление", xAccessorName: "pressure", color: '#0c0' },
|
|
||||||
{ label: "Давление. Холостой ход", xAccessorName: "pressureIdle", color: '#00c' },
|
|
||||||
|
|
||||||
{ label: "Давление. Задание", xAccessorName: "pressureSp", color: '#900' },
|
|
||||||
{ label: "Давление. Задание для роторного бурения", xAccessorName: "pressureSpRotor", color: '#990' },
|
|
||||||
{ label: "Давление. Задание для режима слайда", xAccessorName: "pressureSpSlide", color: '#909' },
|
|
||||||
{ label: "Давление. Задание для проработки", xAccessorName: "pressureSpDevelop", color: '#099' },
|
|
||||||
{ label: "Давление дифф. Аварийное макс.", xAccessorName: "pressureDeltaLimitMax", color: '#090' },
|
|
||||||
{ label: "Осевая нагрузка", xAccessorName: "axialLoad", color: '#009' },
|
|
||||||
|
|
||||||
{ label: "Осевая нагрузка. Задание", xAccessorName: "axialLoadSp", color: '#600' },
|
|
||||||
{ label: "Осевая нагрузка. Аварийная макс.", xAccessorName: "axialLoadLimitMax", color: '#660' },
|
|
||||||
{ label: "Вес на крюке", xAccessorName: "hookWeight", color: '#606' },
|
|
||||||
{ label: "Вес на крюке. Холостой ход", xAccessorName: "hookWeightIdle", color: '#066' },
|
|
||||||
{ label: "Вес на крюке. Посадка", xAccessorName: "hookWeightLimitMin", color: '#060' },
|
|
||||||
{ label: "Вес на крюке. Затяжка", xAccessorName: "hookWeightLimitMax", color: '#006' },
|
|
||||||
|
|
||||||
{ label: "Момент на роторе", xAccessorName: "rotorTorque", color: '#300' },
|
|
||||||
{ label: "Момент на роторе. Холостой ход", xAccessorName: "rotorTorqueIdle", color: '#330' },
|
|
||||||
{ label: "Момент на роторе. Задание", xAccessorName: "rotorTorqueSp", color: '#303' },
|
|
||||||
{ label: "Момент на роторе. Аварийный макс.", xAccessorName: "rotorTorqueLimitMax", color: '#033' },
|
|
||||||
{ label: "Обороты ротора", xAccessorName: "rotorSpeed", color: '#030' },
|
|
||||||
{ label: "Расход", xAccessorName: "flow", color: '#003' },
|
|
||||||
|
|
||||||
{ label: "Расход. Холостой ход", xAccessorName: "flowIdle", color: '#666' },
|
|
||||||
{ label: "Расход. Аварийный макс.", xAccessorName: "flowDeltaLimitMax", color: '#ccc' },
|
|
||||||
]
|
|
||||||
|
|
||||||
const tagRender = ({ label, value, closable, onClose }) =>{
|
|
||||||
const onPreventMouseDown = event => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
let color = linesCollection.find(l=>l.xAccessorName === value)?.color
|
|
||||||
return (
|
|
||||||
<Tag
|
|
||||||
onMouseDown={onPreventMouseDown}
|
|
||||||
closable={closable}
|
|
||||||
onClose={onClose}
|
|
||||||
style={{ marginRight: 3 }}>
|
|
||||||
<span style={{backgroundColor:color}}> </span>
|
|
||||||
<span> {label}</span>
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ArchiveColumn({ data, config, rangeDate, chartRatio, onRemoveChart, onSaveConfig }) {
|
|
||||||
const [lines, setLines] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLines(config.lines);
|
|
||||||
},[config]);
|
|
||||||
|
|
||||||
const handleLinesSetChange = (linesKeys) => {
|
|
||||||
let newLines = linesCollection.filter(line => linesKeys.includes(line.xAccessorName));
|
|
||||||
config.lines = newLines;
|
|
||||||
if(onSaveConfig)
|
|
||||||
onSaveConfig()
|
|
||||||
setLines(newLines);
|
|
||||||
};
|
|
||||||
|
|
||||||
let selectedValues = lines?.map(line=>line.xAccessorName)??[]
|
|
||||||
|
|
||||||
const select = <Select
|
|
||||||
mode="multiple"
|
|
||||||
placeholder="Выберите линии"
|
|
||||||
value={selectedValues}
|
|
||||||
allowClear={false}
|
|
||||||
showArrow
|
|
||||||
bordered={false}
|
|
||||||
tagRender={tagRender}
|
|
||||||
onChange={handleLinesSetChange}
|
|
||||||
style={{
|
|
||||||
minWidth: "300px",
|
|
||||||
maxWidth: "400px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{linesCollection.map((line) => (<Option key={line.xAccessorName} value={line.xAccessorName} color={line.color}>{line.label}</Option>))}
|
|
||||||
</Select>;
|
|
||||||
|
|
||||||
const popBar = <Row>
|
|
||||||
{select}
|
|
||||||
<Tooltip title="Удалить этот график">
|
|
||||||
<Button onClick={() => onRemoveChart(config.id)}><DeleteOutlined /></Button>
|
|
||||||
</Tooltip>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Popover content={popBar}>
|
|
||||||
<div>
|
|
||||||
<ChartTimeArchive
|
|
||||||
data={data}
|
|
||||||
yDisplay={config.yDisplay}
|
|
||||||
lines={lines}
|
|
||||||
rangeDate={rangeDate}
|
|
||||||
chartRatio={chartRatio}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
</>);
|
|
||||||
}
|
|
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
|
@ -1,16 +0,0 @@
|
|||||||
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
|
||||||
|
|
||||||
const defaultOptionsDepthLabels = {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: black,
|
|
||||||
borderRadius: 4,
|
|
||||||
clamp: true,
|
|
||||||
display: true,
|
|
||||||
data: {
|
|
||||||
datasets: [{
|
|
||||||
datalabels: {
|
|
||||||
color: '#0f4000'
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
import moment from 'moment';
|
|
||||||
import { useEffect, useState} from 'react';
|
|
||||||
import {ChartTimeBase} from './ChartTimeBase'
|
|
||||||
|
|
||||||
export const GetRandomColor = () => "#" + Math.floor(Math.random()*16777215).toString(16)
|
|
||||||
|
|
||||||
export const CreateDataset = (lineConfig) => {
|
|
||||||
let color = lineConfig.borderColor
|
|
||||||
?? lineConfig.backgroundColor
|
|
||||||
?? lineConfig.color
|
|
||||||
?? GetRandomColor()
|
|
||||||
|
|
||||||
let datasets = {
|
|
||||||
label: lineConfig.label,
|
|
||||||
data: [],
|
|
||||||
backgroundColor: lineConfig.backgroundColor ?? color,
|
|
||||||
borderColor: lineConfig.borderColor ?? color,
|
|
||||||
borderWidth: lineConfig.borderWidth ?? 1,
|
|
||||||
borderDash: lineConfig.dash ?? [],
|
|
||||||
}
|
|
||||||
return datasets
|
|
||||||
}
|
|
||||||
|
|
||||||
const ChartOptions = {
|
|
||||||
// responsive: true,
|
|
||||||
// plugins:{
|
|
||||||
// legend:{
|
|
||||||
// display: false,
|
|
||||||
// maxHeight: 64,
|
|
||||||
// fullSize: true,
|
|
||||||
// position: 'chartArea',
|
|
||||||
// align: 'start',
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChartTimeArchive = ({lines, data, yDisplay, rangeDate, chartRatio}) => {
|
|
||||||
const [dataParams, setDataParams] = useState({data:{datasets: []}})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if ((!lines)
|
|
||||||
|| (!data))
|
|
||||||
return
|
|
||||||
|
|
||||||
let newDatasets = lines.map(lineCfg => {
|
|
||||||
let dataset = CreateDataset(lineCfg)
|
|
||||||
dataset.data = data.map(dataItem => {
|
|
||||||
return {
|
|
||||||
x: dataItem[lineCfg.xAccessorName],
|
|
||||||
y: new Date(dataItem[lineCfg.yAccessorName??'date'])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return dataset
|
|
||||||
})
|
|
||||||
|
|
||||||
let interval = rangeDate ? (rangeDate[1] - rangeDate[0]) / 1000 : null
|
|
||||||
let startDate = rangeDate ? rangeDate[0] : moment()
|
|
||||||
let newParams = {
|
|
||||||
yInterval: interval,
|
|
||||||
yStart: startDate,
|
|
||||||
displayLabels: yDisplay??false,
|
|
||||||
data: {
|
|
||||||
datasets: newDatasets
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setDataParams(newParams)
|
|
||||||
}, [data, lines, yDisplay, rangeDate, chartRatio])
|
|
||||||
|
|
||||||
const opt = ChartOptions
|
|
||||||
opt.aspectRatio = chartRatio
|
|
||||||
|
|
||||||
return (<ChartTimeBase dataParams={dataParams} options={opt}/>)
|
|
||||||
}
|
|
@ -71,6 +71,7 @@ const defaultOptions = {
|
|||||||
position:'top'
|
position:'top'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
parsing: false,
|
||||||
elements:{
|
elements:{
|
||||||
point:{
|
point:{
|
||||||
radius:0,
|
radius:0,
|
||||||
@ -196,30 +197,27 @@ export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams
|
|||||||
const chartRef = useRef<HTMLCanvasElement>(null)
|
const chartRef = useRef<HTMLCanvasElement>(null)
|
||||||
const [chart, setChart] = useState<any>()
|
const [chart, setChart] = useState<any>()
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
if((chartRef.current)&&(!chart)){
|
let thisOptions = {}
|
||||||
let thisOptions = {}
|
Object.assign(thisOptions, defaultOptions, options)
|
||||||
Object.assign(thisOptions, defaultOptions, options)
|
|
||||||
|
|
||||||
let newChart = new Chart(chartRef.current, {
|
let newChart = new Chart(chartRef.current, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
plugins: [ChartDataLabels],
|
plugins: [ChartDataLabels],
|
||||||
options: thisOptions,
|
options: thisOptions,
|
||||||
data: dataParams.data
|
data: { datasets: [] }
|
||||||
})
|
})
|
||||||
setChart(newChart)
|
setChart(newChart)
|
||||||
|
return () => newChart?.destroy()
|
||||||
return () => chart?.destroy()
|
}, [options])
|
||||||
}
|
|
||||||
|
|
||||||
if(!chart) return
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!chart) return;
|
||||||
chart.data = dataParams.data
|
chart.data = dataParams.data
|
||||||
chart.options.aspectRatio = options?.aspectRatio
|
|
||||||
if(dataParams.yStart){
|
if(dataParams.yStart){
|
||||||
const interval = Number(dataParams.yInterval ?? 600)
|
|
||||||
const start = new Date(dataParams.yStart)
|
const start = new Date(dataParams.yStart)
|
||||||
const end = new Date(dataParams.yStart)
|
const end = new Date(dataParams.yStart)
|
||||||
|
const interval = Number(dataParams.yInterval ?? 600)
|
||||||
end.setSeconds(end.getSeconds() + interval)
|
end.setSeconds(end.getSeconds() + interval)
|
||||||
const { unit, stepSize } = timeParamsByInterval(interval)
|
const { unit, stepSize } = timeParamsByInterval(interval)
|
||||||
|
|
||||||
@ -233,7 +231,7 @@ export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams
|
|||||||
}
|
}
|
||||||
|
|
||||||
chart.update()
|
chart.update()
|
||||||
}, [chart, dataParams, options])
|
}, [chart, dataParams])
|
||||||
|
|
||||||
return(<canvas ref={chartRef} />)
|
return(<canvas ref={chartRef} />)
|
||||||
}
|
}
|
85
src/components/charts/Column.jsx
Normal file
85
src/components/charts/Column.jsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import React, { 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, additionalPointData, interval, yDisplay, yStart, 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,
|
||||||
|
...additionalPointData?.(dataItem, lineCfg)
|
||||||
|
})).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)
|
||||||
|
|
||||||
|
dataset.data = points
|
||||||
|
})
|
||||||
|
|
||||||
|
preDataParams.yStart = yStart
|
||||||
|
preDataParams.yInterval = interval
|
||||||
|
preDataParams.displayLabels = yDisplay
|
||||||
|
|
||||||
|
postParsing?.(preDataParams)
|
||||||
|
return {...preDataParams}
|
||||||
|
})
|
||||||
|
|
||||||
|
}, [data, lineGroup, interval, yDisplay, yStart, postParsing, savePreviousData, additionalPointData])
|
||||||
|
|
||||||
|
return <ChartTimeBase dataParams = { dataParams } options = { chartPluginsOptions } />
|
||||||
|
}
|
@ -1,144 +0,0 @@
|
|||||||
import { useRef, useLayoutEffect, useState, useEffect } from 'react'
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
DatePicker,
|
|
||||||
Row,
|
|
||||||
Col,
|
|
||||||
Tooltip} from 'antd'
|
|
||||||
import { TelemetryDataSaubService } from '../services/api'
|
|
||||||
import {generateUUID} from '../services/UidGenerator'
|
|
||||||
import { ArchiveColumn } from '../components/ArchiveColumn'
|
|
||||||
import moment from 'moment'
|
|
||||||
import { notify } from "../components/factory"
|
|
||||||
import LoaderPortal from '../components/LoaderPortal'
|
|
||||||
|
|
||||||
const { RangePicker } = DatePicker
|
|
||||||
|
|
||||||
const SaveObject = (key, obj) => {
|
|
||||||
let json = JSON.stringify(obj)
|
|
||||||
localStorage.setItem(key, json)
|
|
||||||
}
|
|
||||||
|
|
||||||
const LoadObject = (key) => {
|
|
||||||
let json = localStorage.getItem(key)
|
|
||||||
return json ? JSON.parse(json) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Archive({idWell}) {
|
|
||||||
const [saubData, setSaubData] = useState([])
|
|
||||||
const [chartsCfgs, setChartsCfgs] = useState([])
|
|
||||||
const [rangeDate, setRangeDate] = useState([moment().subtract(3,'hours'), moment()])
|
|
||||||
const [geometry, setGeometry] = useState({ratioRest:1, ratio1st:1, wRest:.5, w1st:.5})
|
|
||||||
const [loader, setLoader] = useState(false)
|
|
||||||
const chartsCfgsKey = 'chartsCfgs'
|
|
||||||
const chartsContainerRef = useRef();
|
|
||||||
|
|
||||||
const handleReceiveDataSaub = (data) => {
|
|
||||||
if (data)
|
|
||||||
setSaubData(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onAddChart = () => {
|
|
||||||
let newChartCfgs = [...chartsCfgs, {id: generateUUID(), yDisplay: false, aspectRatio:1}]
|
|
||||||
setChartsCfgs(newChartCfgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onRemoveChart = (id) => {
|
|
||||||
let newChartCfgs = chartsCfgs.filter(cfg => cfg.id !== id )
|
|
||||||
setChartsCfgs(newChartCfgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSaveConfig = ()=>{
|
|
||||||
SaveObject(chartsCfgsKey, chartsCfgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onChangeRange = (range) => {
|
|
||||||
setRangeDate(range)
|
|
||||||
}
|
|
||||||
|
|
||||||
useLayoutEffect(()=>{
|
|
||||||
if(chartsContainerRef.current && chartsCfgs?.length){
|
|
||||||
let w = chartsContainerRef.current.offsetWidth //1792
|
|
||||||
w = w > 0 ? w : 1792
|
|
||||||
let ot = chartsContainerRef.current.offsetTop
|
|
||||||
let ph = chartsContainerRef.current.offsetParent.offsetHeight
|
|
||||||
let h = ph - ot - 32 //761
|
|
||||||
h = h > 0 ? h : 761
|
|
||||||
|
|
||||||
let chartsCount = chartsCfgs.length
|
|
||||||
|
|
||||||
let labelLenght = 8
|
|
||||||
let borderWidth = 8
|
|
||||||
let wRest = Math.floor((w - labelLenght)/chartsCount) - borderWidth
|
|
||||||
let w1st = wRest + labelLenght
|
|
||||||
|
|
||||||
let ratio1st = w1st/h
|
|
||||||
let ratioRest = wRest/h
|
|
||||||
|
|
||||||
setGeometry({ratio1st, ratioRest, w1st, wRest})
|
|
||||||
}
|
|
||||||
},[chartsContainerRef, chartsCfgs])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let cfgs = LoadObject(chartsCfgsKey)
|
|
||||||
if(cfgs)
|
|
||||||
setChartsCfgs(cfgs)
|
|
||||||
},[])
|
|
||||||
|
|
||||||
useEffect(()=>{
|
|
||||||
SaveObject(chartsCfgsKey, chartsCfgs)
|
|
||||||
},[chartsCfgs])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let interval = (rangeDate[1] - rangeDate[0]) / 1000
|
|
||||||
let startDate = rangeDate[0].toISOString()
|
|
||||||
|
|
||||||
setLoader(true)
|
|
||||||
TelemetryDataSaubService.getData(idWell, startDate, interval, 2048)
|
|
||||||
.then(handleReceiveDataSaub)
|
|
||||||
.catch(error => {
|
|
||||||
notify(`Не удалось загрузить данные по скважине (${idWell}) c ${rangeDate[0]} по ${rangeDate[1]}`, 'error')
|
|
||||||
console.error(error)
|
|
||||||
})
|
|
||||||
.finally(()=>setLoader(false))
|
|
||||||
}, [idWell, rangeDate]);
|
|
||||||
|
|
||||||
let charts = null
|
|
||||||
if(chartsCfgs.length > 0){
|
|
||||||
chartsCfgs[0].yDisplay = true
|
|
||||||
|
|
||||||
charts = chartsCfgs.map((cfg, i) =>
|
|
||||||
<Col flex={`${i===0 ? geometry.w1st : geometry.wRest}px`}
|
|
||||||
key={cfg.id}>
|
|
||||||
<ArchiveColumn
|
|
||||||
data={saubData}
|
|
||||||
rangeDate={rangeDate}
|
|
||||||
chartRatio={i===0 ? geometry.ratio1st : geometry.ratioRest}
|
|
||||||
onRemoveChart={onRemoveChart}
|
|
||||||
onSaveConfig={onSaveConfig}
|
|
||||||
config={cfg}/>
|
|
||||||
</Col>)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (<>
|
|
||||||
<Tooltip title="Добавить график">
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={onAddChart}
|
|
||||||
disabled={chartsCfgs.length >= 6}>
|
|
||||||
+
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
<RangePicker
|
|
||||||
showTime
|
|
||||||
allowClear={false}
|
|
||||||
onChange = {onChangeRange}
|
|
||||||
value = {rangeDate}
|
|
||||||
/>
|
|
||||||
<LoaderPortal show={loader}>
|
|
||||||
<Row ref={chartsContainerRef}>
|
|
||||||
{charts}
|
|
||||||
</Row>
|
|
||||||
</LoaderPortal>
|
|
||||||
</>)
|
|
||||||
}
|
|
35
src/pages/Archive/ArchiveColumn.jsx
Normal file
35
src/pages/Archive/ArchiveColumn.jsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Grid, GridItem } from '../../components/Grid'
|
||||||
|
import { Column } from '../../components/charts/Column'
|
||||||
|
|
||||||
|
export const ArchiveColumn = ({ lineGroup, data, interval, style, headerHeight, yStart }) => {
|
||||||
|
const [lineGroupWithoutShapes, setLineGroupWithoutShapes] = useState([])
|
||||||
|
const [pv, setPV] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const lgws = lineGroup.filter(cfg => !cfg.isShape)
|
||||||
|
setLineGroupWithoutShapes(lgws)
|
||||||
|
setPV(lgws.filter(line => line.showLabels).map(line => ({
|
||||||
|
color: line.color,
|
||||||
|
label: line.label
|
||||||
|
})))
|
||||||
|
}, [lineGroup])
|
||||||
|
|
||||||
|
|
||||||
|
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={lineGroupWithoutShapes}
|
||||||
|
interval={interval}
|
||||||
|
yDisplay={false}
|
||||||
|
yStart={yStart}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
66
src/pages/Archive/ArchiveDisplay.jsx
Normal file
66
src/pages/Archive/ArchiveDisplay.jsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Grid, GridItem } from '../../components/Grid'
|
||||||
|
import { ArchiveColumn } from './ArchiveColumn'
|
||||||
|
import { paramsGroups } from '../TelemetryView'
|
||||||
|
|
||||||
|
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 = ({data, startDate, interval, onWheel}) => {
|
||||||
|
const [chartData, setChartData] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const endDate = new Date(+startDate + interval)
|
||||||
|
setChartData(cutData(data, startDate, 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>
|
||||||
|
)
|
||||||
|
}
|
146
src/pages/Archive/index.jsx
Normal file
146
src/pages/Archive/index.jsx
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
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, cutData } from './ArchiveDisplay'
|
||||||
|
import { normalizeData, sortByDate } from '../TelemetryView'
|
||||||
|
|
||||||
|
const DATA_COUNT = 2048 // Колличество точек на подгрузку графика
|
||||||
|
const ADDITIVE_PAGES = 2 // Дополнительные данные для графиков
|
||||||
|
const LOADING_TRIGGER = 0.5
|
||||||
|
const WHEEL_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 = +startDate - interval * ADDITIVE_PAGES
|
||||||
|
let loadingEndDate = +startDate + interval * (ADDITIVE_PAGES + 1)
|
||||||
|
|
||||||
|
const newLoaded = {
|
||||||
|
start: loadingStartDate,
|
||||||
|
end: loadingEndDate
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loaded) {
|
||||||
|
if (loadingStartDate >= loaded.start)
|
||||||
|
loadingStartDate = loaded.end
|
||||||
|
if (loadingEndDate <= loaded.end)
|
||||||
|
loadingEndDate = loaded.start
|
||||||
|
newLoaded.start = Math.min(loaded.start, loadingStartDate)
|
||||||
|
newLoaded.end = Math.max(loaded.end, loadingEndDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadingInterval = Math.trunc((loadingEndDate - loadingStartDate) / 1000)
|
||||||
|
|
||||||
|
return {
|
||||||
|
loadingStartDate: new Date(loadingStartDate),
|
||||||
|
newLoaded: {
|
||||||
|
start: new Date(newLoaded.start),
|
||||||
|
end: new Date(newLoaded.end)
|
||||||
|
},
|
||||||
|
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 onGraphWheel = (e) => {
|
||||||
|
if (loaded) {
|
||||||
|
setStartDate((prevStartDate) => {
|
||||||
|
const offset = e.deltaY * chartInterval * WHEEL_SENSITIVITY
|
||||||
|
const nextStartDate = new Date(+prevStartDate + offset)
|
||||||
|
const lastPossibleDate = new Date(Math.min(loaded.end, new Date()) - chartInterval)
|
||||||
|
return new Date(Math.max(loaded.start, Math.min(nextStartDate, lastPossibleDate)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
|
const dates = await TelemetryDataSaubService.getDataDatesRange(idWell)
|
||||||
|
let startDate
|
||||||
|
if (dates?.from && dates?.to)
|
||||||
|
startDate = Math.max(new Date(dates.from), +new Date(dates.to) - chartInterval)
|
||||||
|
else
|
||||||
|
startDate = +new Date() - chartInterval
|
||||||
|
setStartDate(new Date(startDate))
|
||||||
|
},
|
||||||
|
setShowLoader,
|
||||||
|
`Не удалось загрузить диапозон телеметрии для скважины "${idWell}"`
|
||||||
|
), [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setStartDate((startDate) => new Date(Math.min(+new Date() - chartInterval, startDate)))
|
||||||
|
setDataSaub([])
|
||||||
|
}, [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, ...normalizeData(data)]
|
||||||
|
newData.sort(sortByDate)
|
||||||
|
return cutData(newData, loadedStartDate, 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,131 +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 }) => {
|
|
||||||
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 > 1024)
|
|
||||||
lineData.splice(0, (1024 - lineData.length))
|
|
||||||
|
|
||||||
dataset.data = lineData
|
|
||||||
|
|
||||||
//Area
|
|
||||||
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 = new Date(Math.max(new Date(dataLast.date), preDataParams.yStart ?? new Date(0)))
|
|
||||||
preDataParams.yStart.setSeconds(preDataParams.yStart.getSeconds() - interval * 0.97)
|
|
||||||
preDataParams.yInterval = interval
|
|
||||||
preDataParams.displayLabels = yDisplay ?? false
|
|
||||||
return {...preDataParams}
|
|
||||||
})
|
|
||||||
|
|
||||||
}, [data, lineGroup, interval, yDisplay, flowChartData, dataLast])
|
|
||||||
|
|
||||||
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>
|
|
||||||
<ChartTimeBase dataParams = {dataParams} options = { chartPluginsOptions } />
|
|
||||||
</div>
|
|
||||||
<ChartTimeOnlineFooter data={dataLast} lineGroup={lineGroup} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
95
src/pages/TelemetryView/MonitoringColumn.jsx
Normal file
95
src/pages/TelemetryView/MonitoringColumn.jsx
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
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()) ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
const RemoveSimilar = (input) => {
|
||||||
|
const data = [input[0]]
|
||||||
|
for (let i = 1; i < input.length; i++)
|
||||||
|
if (input[i].y !== input[i - 1].y)
|
||||||
|
data.push(input[i])
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MonitoringColumn = ({ lineGroup, data, flowChartData, interval, showBorder, style, headerHeight, pointCount }) => {
|
||||||
|
const [lineGroupWithoutShapes, setLineGroupWithoutShapes] = useState([])
|
||||||
|
const dataLast = data?.[data.length - 1]
|
||||||
|
const yStart = new Date(+(dataLast?.date ? new Date(dataLast.date) : new Date()) - interval * 1000 * 0.97)
|
||||||
|
const pv = lineGroup.filter(line => line.showLabels).map(line => ({
|
||||||
|
color: line.color,
|
||||||
|
label: line.label,
|
||||||
|
unit: line.units,
|
||||||
|
value: dataLast?.[line.xAccessorName]
|
||||||
|
}))
|
||||||
|
|
||||||
|
const addPointData = (point) => ({ depth: point.wellDepth })
|
||||||
|
|
||||||
|
const postParsing = (data) => {
|
||||||
|
lineGroupWithoutShapes.forEach(lineCfg => {
|
||||||
|
const lineDataSet = GetOrCreateDatasetByLineConfig(data.data, lineCfg)
|
||||||
|
|
||||||
|
lineDataSet.data = RemoveSimilar(lineDataSet.data)
|
||||||
|
if (lineDataSet.data.length > pointCount)
|
||||||
|
lineDataSet.data.splice(0, pointCount - lineDataSet.data.length)
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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}
|
||||||
|
additionalPointData={addPointData}
|
||||||
|
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 { useState, useEffect } from 'react'
|
||||||
import { Select } from 'antd'
|
import { Select } from 'antd'
|
||||||
|
|
||||||
import { Column } from './Column'
|
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'
|
||||||
@ -22,6 +22,7 @@ import MomentStabPicEnabled from '../../images/DempherOn.png'
|
|||||||
import MomentStabPicDisabled from '../../images/DempherOff.png'
|
import MomentStabPicDisabled from '../../images/DempherOff.png'
|
||||||
import SpinPicEnabled from '../../images/SpinEnabled.png'
|
import SpinPicEnabled from '../../images/SpinEnabled.png'
|
||||||
import SpinPicDisabled from '../../images/SpinDisabled.png'
|
import SpinPicDisabled from '../../images/SpinDisabled.png'
|
||||||
|
import { PeriodPicker, defaultPeriod } from '../../components/PeriodPicker'
|
||||||
|
|
||||||
import '../../styles/message.css'
|
import '../../styles/message.css'
|
||||||
|
|
||||||
@ -250,21 +251,15 @@ const rotorTorqueGroup = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const paramsGroups = [blockHeightGroup, blockSpeedGroup, pressureGroup, axialLoadGroup, hookWeightGroup, rotorTorqueGroup]
|
export const paramsGroups = [
|
||||||
|
blockHeightGroup,
|
||||||
const timePeriodCollection = [
|
blockSpeedGroup,
|
||||||
{ value: '60', label: '1 минута' },
|
pressureGroup,
|
||||||
{ value: '300', label: '5 минут' },
|
axialLoadGroup,
|
||||||
{ value: '600', label: '10 минут' },
|
hookWeightGroup,
|
||||||
{ value: '1800', label: '30 минут' },
|
rotorTorqueGroup
|
||||||
{ value: '3600', label: '1 час' },
|
|
||||||
{ value: '21600', label: '6 часов' },
|
|
||||||
{ value: '43200', label: '12 часов' },
|
|
||||||
{ value: '86400', label: '24 часа' }
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const defaultChartInterval = '600'
|
|
||||||
|
|
||||||
const getLast = (data) =>
|
const getLast = (data) =>
|
||||||
Array.isArray(data) ? data.slice(-1)[0] : data
|
Array.isArray(data) ? data.slice(-1)[0] : data
|
||||||
|
|
||||||
@ -295,27 +290,27 @@ const getIndexOfDrillingBy = (dataSaub) => {
|
|||||||
return order[idFeedRegulator] ?? -1
|
return order[idFeedRegulator] ?? -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const sortByDate = (a, b) => a.date > b.date ? 1 : -1
|
||||||
|
export const normalizeData = (data) => data?.map(item => ({
|
||||||
|
...item,
|
||||||
|
rotorSpeed: item.rotorSpeed < 1 ? 0 : item.rotorSpeed,
|
||||||
|
rotorTorque: item.rotorTorque < 1 ? 0 : item.rotorTorque,
|
||||||
|
blockSpeed: Math.abs(item.blockSpeed)
|
||||||
|
})) ?? []
|
||||||
|
|
||||||
export default function TelemetryView({ idWell }) {
|
export default function TelemetryView({ idWell }) {
|
||||||
const [dataSaub, setDataSaub] = useState([])
|
const [dataSaub, setDataSaub] = useState([])
|
||||||
const [dataSpin, setDataSpin] = useState([])
|
const [dataSpin, setDataSpin] = useState([])
|
||||||
const [chartInterval, setChartInterval] = useState(defaultChartInterval)
|
const [chartInterval, setChartInterval] = useState(defaultPeriod)
|
||||||
const [wellData, setWellData] = useState({ idState: 0 })
|
const [wellData, setWellData] = useState({ idState: 0 })
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [flowChartData, setFlowChartData] = useState([])
|
const [flowChartData, setFlowChartData] = useState([])
|
||||||
|
|
||||||
const options = timePeriodCollection.map((line) => <Option key={line.value}>{line.label}</Option>)
|
|
||||||
|
|
||||||
const handleDataSaub = (data) => {
|
const handleDataSaub = (data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
data.forEach((_, idx) => {
|
const dataSaub = normalizeData(data)
|
||||||
if (data[idx].rotorSpeed < 1)
|
dataSaub.sort(sortByDate)
|
||||||
data[idx].rotorSpeed = 0;
|
setDataSaub(dataSaub)
|
||||||
if (data[idx].rotorTorque < 1)
|
|
||||||
data[idx].rotorTorque = 0;
|
|
||||||
data[idx].blockSpeed = Math.abs(data[idx].blockSpeed)
|
|
||||||
})
|
|
||||||
data.sort((a, b) => a.date > b.date ? 1 : -1)
|
|
||||||
setDataSaub(data)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,9 +370,7 @@ export default function TelemetryView({ idWell }) {
|
|||||||
<ModeDisplay data={dataSaub} />
|
<ModeDisplay data={dataSaub} />
|
||||||
<div style={{ marginLeft: '1rem' }}>
|
<div style={{ marginLeft: '1rem' }}>
|
||||||
Интервал:
|
Интервал:
|
||||||
<Select defaultValue={defaultChartInterval} onChange={setChartInterval}>
|
<PeriodPicker onChange={setChartInterval} />
|
||||||
{options}
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginLeft: '1rem' }}>
|
<div style={{ marginLeft: '1rem' }}>
|
||||||
Статус:
|
Статус:
|
||||||
@ -399,14 +392,16 @@ export default function TelemetryView({ idWell }) {
|
|||||||
</GridItem>
|
</GridItem>
|
||||||
{paramsGroups.map((group, index) =>
|
{paramsGroups.map((group, index) =>
|
||||||
<GridItem col={2 + index} row={'2'} className={'border_small'} key={`${group.label}${index}`} style={{padding:0}}>
|
<GridItem col={2 + index} row={'2'} className={'border_small'} key={`${group.label}${index}`} style={{padding:0}}>
|
||||||
<Column
|
<MonitoringColumn
|
||||||
|
showLastValues
|
||||||
style={{ width: '13vw' }}
|
style={{ width: '13vw' }}
|
||||||
data={dataSaub}
|
data={dataSaub}
|
||||||
flowChartData={flowChartData}
|
flowChartData={flowChartData}
|
||||||
lineGroup={group}
|
lineGroup={group}
|
||||||
interval={chartInterval}
|
interval={chartInterval}
|
||||||
headerHeight={'50px'}
|
headerHeight={'50px'}
|
||||||
showBorder={getIndexOfDrillingBy(dataSaub) === index} />
|
showBorder={getIndexOfDrillingBy(dataSaub) === index}
|
||||||
|
/>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
)}
|
)}
|
||||||
<GridItem col={'2'} row={'3'} colSpan={'7'}>
|
<GridItem col={'2'} row={'3'} colSpan={'7'}>
|
||||||
|
Loading…
Reference in New Issue
Block a user