diff --git a/src/components/ChartTimeOnlineFooter.jsx b/src/components/ChartTimeOnlineFooter.jsx new file mode 100644 index 0000000..4864e67 --- /dev/null +++ b/src/components/ChartTimeOnlineFooter.jsx @@ -0,0 +1,52 @@ +import {ValueDisplay} from './Display' +import {ControlOutlined} from '@ant-design/icons' +import {Popover} from 'antd' + +export const ChartTimeOnlineFooter = (props) =>{ + const { data, + lineIdle, + lineSp, + linesOther} = props + + let sp = null + let idle = null + + if(data && lineSp) + sp = data[lineSp.xAccessorName] + + if(data && lineIdle) + idle = data[lineIdle.xAccessorName] + + let spField = + + let popContent = linesOther?.map(line =>{ + let val = null + if(data) + val = data[line.xAccessorName] + return ( +
+ {line.label} + +
) + }) + + if(popContent) + spField = +
+ + {spField} +
+
+ else + spField =
+ {spField} +
+ + return(
+ {spField} +
+ х.х. + +
+
) +} \ No newline at end of file diff --git a/src/components/CustomColumn.jsx b/src/components/CustomColumn.jsx new file mode 100644 index 0000000..86cc494 --- /dev/null +++ b/src/components/CustomColumn.jsx @@ -0,0 +1,26 @@ +import {Display} from './Display' + +export const CustomColumn = ({data}) => { + const dataLast = data[data.length -1] + + const lines = [ + {label:'Рот., об/мин', accessorName:'rotorSpeed'}, + {label:'Долото, м', accessorName:'bitDepth'}, + {label:'Забой, м', accessorName:'wellDepth'}, + {label:'Расход, м³/ч', accessorName:'flow'}, + {label:'Расход х.х., м³/ч', accessorName:'flowIdle'}, + ] + + if(dataLast) + lines.forEach(line => line.value = dataLast[line.accessorName]?.toPrecision(4)??'-' ) + else + lines.forEach(line => line.value = '-' ) + + return (<> + {lines.map(line => )} + ) +} \ No newline at end of file diff --git a/src/components/Display.jsx b/src/components/Display.jsx new file mode 100644 index 0000000..e575953 --- /dev/null +++ b/src/components/Display.jsx @@ -0,0 +1,23 @@ +export const ValueDisplay = ({prefix, value, suffix}) =>{ + let val = '---' + + if(value) + if(Number.isFinite(+value)) + val = (+value).toPrecision(4)??'---' + else + val = value + + return({prefix} {val} {suffix}) +} + +export const Display = (props)=>{ + const {label} = props + + return
+
{label}
+
+ +
+
+ +} \ No newline at end of file diff --git a/src/components/ModeDisplay.jsx b/src/components/ModeDisplay.jsx new file mode 100644 index 0000000..f887aca --- /dev/null +++ b/src/components/ModeDisplay.jsx @@ -0,0 +1,27 @@ +const modeNames = { + 0: "Ручной", + 1: "Бурение в роторе", + 2: "Проработка", + 3: "Бурение в слайде", + 4: "Спуск СПО", + 5: "Подъем СПО", + 6: "Подъем с проработкой", + + 10: "БЛОКИРОВКА", +} + +export const ModeDisplay = (props)=>{ + let value = '---' + + if(props.data.length > 0){ + let lastFullData = props.data[props.data.length - 1] + let index = lastFullData['mode'] + if(index >= 0) + value = modeNames[index] ?? index + } + + return(<> + Режим: + {value} + ) +} \ No newline at end of file diff --git a/src/components/Well.jsx b/src/components/Well.jsx new file mode 100644 index 0000000..3814d82 --- /dev/null +++ b/src/components/Well.jsx @@ -0,0 +1,6 @@ + + +export const Well = (props) => { + + return(
) +} \ No newline at end of file diff --git a/src/components/charts/ChartTimeBase.tsx b/src/components/charts/ChartTimeBase.tsx index 3dc6aa7..c740405 100644 --- a/src/components/charts/ChartTimeBase.tsx +++ b/src/components/charts/ChartTimeBase.tsx @@ -14,16 +14,17 @@ import 'chartjs-adapter-date-fns'; Chart.register( TimeScale, LinearScale, LineController, LineElement, PointElement, Legend ); -const defailtOptions = { - maintainAspectRatio: false, - aspectRatio:0.4, +const defaultOptions = { + //maintainAspectRatio: false, + aspectRatio:0.45, animation: false, scales: { y:{ type: 'time', reverse:true, time: { - unit: 'second', + //unit: 'second', + //round:'second', stepSize: 20, displayFormats: { millisecond: 'HH:mm:ss.SSS', @@ -44,7 +45,7 @@ const defailtOptions = { ticks: { z: 1, textStrokeColor : "#ffff", - textStrokeWidth : 1, + textStrokeWidth : 2, color:"#000", } }, @@ -84,6 +85,19 @@ export type ChartTimeBaseProps = { options?: ChartOptions, } +const timeUnitByInterval = (intervalSec:number):String =>{ + if(intervalSec < 24*60*60) + return 'second' + + if(intervalSec < 30*24*60*60) + return 'day' + + if(intervalSec < 365*24*60*60) + return 'week' + else + return 'year' +} + export const ChartTimeBase: React.FC = (props) => { const chartRef = useRef(null) @@ -92,7 +106,7 @@ export const ChartTimeBase: React.FC = (props) => { useEffect(()=>{ if(chartRef.current && (!chart)){ let thisOptions = {} - Object.assign(thisOptions, defailtOptions, props.options) + Object.assign(thisOptions, defaultOptions, props.options) let newChart = new Chart(chartRef.current, { type: 'line', @@ -104,17 +118,14 @@ export const ChartTimeBase: React.FC = (props) => { } }, [chart, props.options, props.dataParams]) - - useEffect(()=>{ if(!chart) return - //mergeDataSets(chart.data, props.dataParams.data) chart.data = props.dataParams.data if(props.dataParams.yStart){ - let interval = props.dataParams.yInterval ?? 600 + let interval = Number(props.dataParams.yInterval ?? 600) let start = new Date(props.dataParams.yStart) let end = new Date(props.dataParams.yStart) end.setSeconds(end.getSeconds() + interval) @@ -123,6 +134,8 @@ export const ChartTimeBase: React.FC = (props) => { chart.options.scales.y.max = end.getTime() chart.options.scales.y.min = start.getTime() chart.options.scales.y.ticks.display = props.dataParams.displayLabels ?? true + chart.options.scales.y.time.unit = timeUnitByInterval(interval) + chart.options.scales.y.time.stepSize = Math.round(interval/24) } } diff --git a/src/components/charts/ChartTime.tsx b/src/components/charts/ChartTimeOnline.tsx similarity index 54% rename from src/components/charts/ChartTime.tsx rename to src/components/charts/ChartTimeOnline.tsx index 711d53e..a1e85ab 100644 --- a/src/components/charts/ChartTime.tsx +++ b/src/components/charts/ChartTimeOnline.tsx @@ -28,8 +28,9 @@ function GetOrCreateDatasetByLineConfig (data: ChartTimeData, lineConfig: LineC export type LineConfig = { type?: string label: string - xAcessorName: string - yAcessorName: string + units?: string + xAccessorName: string + yAccessorName: string color?:string borderColor?: string backgroundColor?: string @@ -39,14 +40,17 @@ export type LineConfig = { } export type ChartTimeProps = { + label?: string, yDisplay: Boolean, + // linePv?: LineConfig, + // lineSp?: LineConfig, + // lineIdle?: LineConfig, lines: LineConfig[], data: any[], interval: number, } -export const ChartTime: React.FC = (props) => { - +export const ChartTimeOnline: React.FC = (props) => { const [dataParams, setDataParams] = useState({data: {datasets:[]}, yStart: new Date(), }) useEffect(()=>{ @@ -56,23 +60,30 @@ export const ChartTime: React.FC = (props) => { || (props.data.length === 0)) return - props.lines.forEach(lineCfg => { - let dataset = GetOrCreateDatasetByLineConfig(dataParams.data, lineCfg) - let points = props.data.map(dataItem => {return{ - x: dataItem[lineCfg.xAcessorName], - y: new Date(dataItem[lineCfg.yAcessorName])} - }) - dataset.data = [ ...dataset.data, ...points, ].slice(-1024) - }); + setDataParams((preDataParams) => { + props.lines.forEach(lineCfg => { + let dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg) + let points = props.data.map(dataItem => {return{ + x: dataItem[lineCfg.xAccessorName], + y: new Date(dataItem[lineCfg.yAccessorName])} + }) + //dataset.data = [ ...dataset.data, ...points,].slice(-1024) + let data = [ ...dataset.data, ...points,] + if(points?.length > 2) + data.sort((a,b) => a.y > b.y ? 1 : -1) + if(data.length > 1024) + data.splice(0, (1024 - data.length)) + dataset.data = data; + }); + + preDataParams.yStart = new Date() + preDataParams.yStart.setSeconds(preDataParams.yStart.getSeconds() - props.interval) + preDataParams.yInterval = props.interval + preDataParams.displayLabels = props.yDisplay + return preDataParams + }) - dataParams.yStart = new Date() - dataParams.yStart.setSeconds(dataParams.yStart.getSeconds() - props.interval) - dataParams.yInterval = props.interval - dataParams.displayLabels = props.yDisplay - - setDataParams(dataParams) - - }, [ props.data, props.lines, props.interval]) + }, [ props.data, props.lines, props.interval, props.yDisplay]) return() } diff --git a/src/pages/Main.jsx b/src/pages/Main.jsx index bc5c657..590c6f6 100644 --- a/src/pages/Main.jsx +++ b/src/pages/Main.jsx @@ -4,7 +4,7 @@ import logo from '../images/logo_32.png' import { useState } from 'react' import { Switch, Route, Redirect, Link} from "react-router-dom" import Wells from './Wells' -import Well from './Well' +import TelemetryView from './TelemetryView' import Files from './Files' const { Header, Content, Sider } = Layout @@ -43,7 +43,19 @@ export default function Main(){ Мониторинг }> - файлы + Архив + + }> + Сообщения + + }> + Рапорт + + }> + Анализ + + }> + Файлы @@ -56,7 +68,7 @@ export default function Main(){ - + diff --git a/src/pages/TelemetryView.jsx b/src/pages/TelemetryView.jsx new file mode 100644 index 0000000..f1da3e2 --- /dev/null +++ b/src/pages/TelemetryView.jsx @@ -0,0 +1,158 @@ +import { useState, useEffect} from 'react'; +import { Row, Col, Select, } from 'antd' +import { ChartTimeOnline} from '../components/charts/ChartTimeOnline' +import { ChartTimeOnlineFooter} from '../components/ChartTimeOnlineFooter' +import { CustomColumn} from '../components/CustomColumn' +import { ModeDisplay } from '../components/ModeDisplay'; +import { Display } from '../components/Display'; + +import { useParams} from 'react-router-dom' +import { Subscribe} from '../services/signalr' +import { DataService} from '../services/api' + +const {Option} = Select + +const dash = [7,3] + +const blockHeightGroup = { + label:"Высота блока", + yDisplay: false, + linePv: { label:"blockHeight", units:'м', xAccessorName : "blockHeight", yAccessorName : "date", color:'#333' }, +} + +const blockSpeedGroup = { + label:"Скорость блока", + yDisplay: false, + linePv: { label:"blockSpeed", units:'м/ч', xAccessorName : "blockSpeed", yAccessorName : "date", color:'#0a0' }, + lineSp: { label:"blockSpeedSp", units:'м/ч', xAccessorName : "blockSpeedSp", yAccessorName : "date", color:'#0a0' }, +} + +const pressureGroup = { + label:"Давтение", + yDisplay: false, + linePv: { label:"pressure", units:'атм', xAccessorName : "pressure", yAccessorName : "date", color:'#c00' }, + lineSp: { label:"pressureSp", units:'атм', xAccessorName : "pressureSp", yAccessorName : "date", color:'#c00' }, + lineIdle: { label:"pressureIdle", units:'атм', xAccessorName : "pressureIdle", yAccessorName : "date", color:'#c00' }, + linesOther: [ + { label:"мекс. перепад", units:'атм', xAccessorName : "pressureDeltaLimitMax", yAccessorName : "date", color:'#c00' }, + ], +} + +const axialLoadGroup = { + label:"Осевая нагрузка", + yDisplay: false, + linePv: { label:"axialLoad", units:'т', xAccessorName : "axialLoad", yAccessorName : "date", color:'#00a' }, + lineSp: { label:"axialLoadSp", units:'т', xAccessorName : "axialLoadSp", yAccessorName : "date", color:'#00a', dash }, + linesOther: [ + { label:"axialLoadLimitMax", units:'т', xAccessorName : "axialLoadLimitMax", yAccessorName : "date", color:'#00a' }, + ], +} + +const hookWeightGroup = { + label:"Ввес на крюке", + yDisplay: false, + linePv: { label:"hookWeight", units:'т', xAccessorName : "hookWeight", yAccessorName : "date", color:'#0aa' }, + lineIdle: { label:"hookWeightIdle", units:'т', xAccessorName : "hookWeightIdle", yAccessorName : "date", color:'#0aa', dash }, + linesOther: [ + { label:"hookWeightLimitMin", units:'т', xAccessorName : "hookWeightLimitMin", yAccessorName : "date", color:'#0aa' }, + { label:"hookWeightLimitMax", units:'т', xAccessorName : "hookWeightLimitMax", yAccessorName : "date", color:'#0aa' }, + ], +} + +const rotorTorqueGroup = { + label:"Момент на роторе", + yDisplay: false, + linePv: { label:"rotorTorque", units:'кН·м', xAccessorName : "rotorTorque", yAccessorName : "date", color:'#a0a' }, + lineSp: { label:"rotorTorqueSp", units:'кН·м', xAccessorName : "rotorTorqueSp", yAccessorName : "date", color:'#a0a' }, + lineIdle: { label:"rotorTorqueIdle", units:'кН·м', xAccessorName : "rotorTorqueIdle", yAccessorName : "date", color:'#a0a' }, + linesOther: [ + { label:"rotorTorqueLimitMax", units:'кН·м', xAccessorName : "rotorTorqueLimitMax", yAccessorName : "date", color:'#a0a' }, + ], +} + +const paramsGroups = [blockHeightGroup, blockSpeedGroup, pressureGroup, axialLoadGroup, hookWeightGroup, rotorTorqueGroup] + +export const Column = ({lineGroup, data, interval})=>{ + let lines = [lineGroup.linePv] + + if(lineGroup.lineSp) + lines.push(lineGroup.lineSp) + + let dataLast = null + let pv = null + if(data?.length > 0 ){ + dataLast = data[data.length - 1]; + if(lineGroup.linePv) + pv = dataLast[lineGroup.linePv?.xAccessorName] + } + + return( + <> + + + + ) +} + +export default function TelemetryView(props){ + let { id } = useParams(); + const [saubData, setSaubData] = useState([]) + const [chartInterval, setchartInterval] = useState(600) + + const handleReceiveDataSaub = (data)=>{ + if(data){ + setSaubData(data) + } + } + + useEffect( ()=> { + DataService.get(id) + .then(handleReceiveDataSaub) + .catch(error=>console.error(error)) + + return Subscribe('ReceiveDataSaub', `well_${id}`, handleReceiveDataSaub) + }, [id]); + + const colSpan = 24/(paramsGroups.length ) + + return(
+ {/*
Well id: {id}; points count: {saubData.length}
*/} + + + + + + Интервал:  + + + + + + + + + + {paramsGroups.map(group=> + + + )} + + + +
) +} \ No newline at end of file diff --git a/src/pages/Well.jsx b/src/pages/Well.jsx deleted file mode 100644 index f30ae07..0000000 --- a/src/pages/Well.jsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useState, useEffect,} from 'react'; -import { Row, Col} from 'antd' -import {ChartTime} from '../components/charts/ChartTime' - -import {useParams} from 'react-router-dom' -import {Subscribe} from '../services/signalr' -import {DataService} from '../services/api' - -const dash = [7,3] - -const lineParameters = [ - { - yDisplay: true, - lines:[ - { label:"blockHeight", xAcessorName : "blockHeight", yAcessorName : "date", color:'#aaf' }, - ], - }, - { - yDisplay: false, - lines:[ - { label:"blockSpeed", xAcessorName : "blockSpeed", yAcessorName : "date", color:'#0a0' }, - { label:"blockSpeedSp", xAcessorName : "blockSpeedSp", yAcessorName : "date", color:'#0a0', dash }, - ], - }, - { - yDisplay: false, - lines:[ - { label:"pressure", xAcessorName : "pressure", yAcessorName : "date", color:'#a00' }, - { label:"pressureSp", xAcessorName : "pressureSp", yAcessorName : "date", color:'#a00', dash }, - ], - }, - { - yDisplay: false, - lines:[ - { label:"axialLoad", xAcessorName : "axialLoad", yAcessorName : "date", color:'#00a' }, - { label:"axialLoadSp", xAcessorName : "axialLoadSp", yAcessorName : "date", color:'#00a', dash }, - ], - }, - { - yDisplay: false, - lines:[ - { label:"вес", xAcessorName : "hookWeight", yAcessorName : "date", color:'#0aa' }, - { label:"вес макс", xAcessorName : "hookWeightLimitMax", yAcessorName : "date", color:'#0aa', dash }, - ], - }, - { - yDisplay: false, - lines:[ - { label:"rotorTorque", xAcessorName : "rotorTorque", yAcessorName : "date", color:'#a0a' }, - { label:"rotorTorqueSp", xAcessorName : "rotorTorqueSp", yAcessorName : "date", color:'#a0a', dash }, - ], - } -] - -const interval = 600 - -export default function Well(props){ - let { id } = useParams(); - const [saubData, setSaubData] = useState([]) - - const handleReceiveDataSaub = (data)=>{ - if(data) - setSaubData(data) - } - - useEffect( ()=> { - DataService.get(id) - .then(handleReceiveDataSaub) - .catch(error=>console.error(error)) - - return Subscribe('ReceiveDataSaub', `well_${id}`, handleReceiveDataSaub) - }, - [id]); - - let colSpan = 24/lineParameters.length - let cols = lineParameters.map(line => - - - ) - - return(
-
Well id: {id}; points count: {saubData.length}
- {cols} - -
) -} \ No newline at end of file diff --git a/src/styles/App.less b/src/styles/App.less index 2b187de..4cf4347 100644 --- a/src/styles/App.less +++ b/src/styles/App.less @@ -58,19 +58,44 @@ min-height: 280; } -#components-layout-demo-top-side-2 .logo { - float: left; - width: 120px; - height: 31px; - margin: 16px 24px 16px 0; - background: rgba(255, 255, 255, 0.3); -} - -.ant-row-rtl #components-layout-demo-top-side-2 .logo { - float: right; - margin: 16px 0 16px 24px; -} - .site-layout-background { background: #fff; } + +.border_small{ + border: 1px solid rgba(0, 0, 0, 0.05); +} + +.display_flex_container{ + display: flex; + flex-wrap: wrap; + flex: auto; +} + +.display_label{ + font-size: 16px; + color: rgb(70, 70, 70); + text-align: left; + justify-content: baseline; + margin: 1px 1rem 1px 1rem; + flex: auto; +} + +.display_value{ + font-size: 18px; + font-weight: bold; + color: rgb(50, 50, 50); + text-align: right; + justify-content: flex-end; + align-items:baseline; + margin: 1px 1rem 1px 1rem; + flex: auto; +} + +.display_small_value{ + color: rgb(50, 50, 50); + text-align: right; + justify-content: baseline; + margin: 1px 1rem 1px 1rem; + flex: auto; +} \ No newline at end of file