Отображение ROP добавлено

This commit is contained in:
goodmice 2021-12-01 14:38:17 +05:00
parent cae068e7b3
commit 46c8b1fe73
5 changed files with 174 additions and 111 deletions

View File

@ -1,4 +1,4 @@
import { useEffect, useRef, useState} from 'react' import { useEffect, useRef, useState } from 'react'
import { import {
Chart, Chart,
TimeScale, TimeScale,
@ -8,8 +8,10 @@ import {
PointElement, PointElement,
LineElement, LineElement,
ChartData, ChartData,
ChartTypeRegistry, ChartOptions,
ChartOptions} from 'chart.js' ChartType,
ChartDataset
} from 'chart.js'
import 'chartjs-adapter-moment' import 'chartjs-adapter-moment'
import ChartDataLabels from 'chartjs-plugin-datalabels' import ChartDataLabels from 'chartjs-plugin-datalabels'
import zoomPlugin from 'chartjs-plugin-zoom' import zoomPlugin from 'chartjs-plugin-zoom'
@ -25,19 +27,13 @@ Chart.register(
zoomPlugin zoomPlugin
) )
const defaultOptions = { const defaultOptions: ChartOptions = {
responsive: true, responsive: true,
aspectRatio: 0.45, aspectRatio: 0.45,
animation: false, animation: false,
tooltips: {
enabled: true,
callbacks: {
label: (tooltipItem:any) => tooltipItem.yLabel
}
},
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
scales: { scales: {
y:{ y: {
type: 'time', type: 'time',
reverse: true, reverse: true,
time: { time: {
@ -54,7 +50,7 @@ const defaultOptions = {
year: 'yyyy.MM', year: 'yyyy.MM',
}, },
}, },
grid:{ grid: {
drawTicks: false, drawTicks: false,
}, },
ticks: { ticks: {
@ -66,20 +62,20 @@ const defaultOptions = {
} }
}, },
x:{ x: {
type:'linear', type: 'linear',
position:'top' position: 'top'
} }
}, },
parsing: false, parsing: false,
elements:{ elements: {
point:{ point: {
radius:0, radius: 0,
hoverRadius:5, hoverRadius: 5,
}, },
}, },
plugins:{ plugins: {
legend:{ legend: {
display: false, display: false,
}, },
datalabels: { datalabels: {
@ -97,100 +93,89 @@ const defaultOptions = {
mode: 'x', mode: 'x',
} }
}, },
tooltip: {
enabled: true,
callbacks: {
label: (tooltipItem: any) => tooltipItem.yLabel
}
},
} }
} }
export type ChartTimeData = ChartData<keyof ChartTypeRegistry, { export type ChartTimeDataPoint = {
x: number; x: number
label: number; label: number
y: Date; y: Date
}[], unknown> }
export type ChartTimeDataset = ChartDataset<ChartType, ChartTimeDataPoint[]>
export type ChartTimeData = ChartData<ChartType, ChartTimeDataPoint[], unknown>
export type ChartTimeDataParams = { export type ChartTimeDataParams = {
data: ChartTimeData, data: ChartTimeData
yStart?: Date, yStart?: Date
yInterval?: number, yInterval?: number
displayLabels?: Boolean, displayLabels?: boolean
} }
export type ChartTimeBaseProps = { export type ChartTimeBaseProps = {
dataParams: ChartTimeDataParams, dataParams: ChartTimeDataParams
// TODO: Create good type for options options?: ChartOptions
options?: ChartOptions<keyof ChartTypeRegistry> | any,
} }
export type TimeParams = { export type TimeParams = {
unit: String unit: string
stepSize: number stepSize: number
} }
const linesPerInterval = 32 const linesPerInterval = 32
export const timeUnitByInterval = (intervalSec: number): String => { const intervals = {
if(intervalSec <= 60) millisecond: 0.001,
second: 1,
minute: 60,
hour: 60 * 60,
day: 60 * 60 * 24,
week: 60 * 60 * 24 * 7,
month: 60 * 60 * 24 * 30,
quarter: 60 * 60 * 24 * 91,
year: 60 * 60 * 24 * 365.25
}
type IntervalType = keyof typeof intervals
export const timeUnitByInterval = (intervalSec: number): IntervalType => {
if(intervalSec <= intervals.minute)
return 'millisecond' return 'millisecond'
if(intervalSec <= 32*60) if(intervalSec <= 32 * intervals.minute)
return 'second' return 'second'
if(intervalSec <= 32*60*60) if(intervalSec <= 32 * intervals.hour)
return 'minute' return 'minute'
if(intervalSec <= 32*12*60*60) if(intervalSec <= 32 * intervals.day / 2)
return 'hour' return 'hour'
if(intervalSec <= 32*24*60*60) if(intervalSec <= 32 * intervals.day)
return 'day' return 'day'
if(intervalSec <= 32*7*24*60*60) if(intervalSec <= 32 * intervals.week)
return 'week' return 'week'
if(intervalSec <= 32*30.4375*24*60*60) if(intervalSec <= 32 * intervals.year / 12)
return 'month' return 'month'
if(intervalSec <= 32*121.75*24*60*60) if(intervalSec <= 32 * intervals.year / 3)
return 'quarter' return 'quarter'
else
return 'year' return 'year'
} }
export const timeParamsByInterval = (intervalSec: number): TimeParams => { export const timeParamsByInterval = (intervalSec: number): TimeParams => {
let stepSize = intervalSec const unit = timeUnitByInterval(intervalSec)
let unit = timeUnitByInterval(intervalSec) const stepSize = Math.max(1, Math.round(intervalSec / intervals[unit] / linesPerInterval))
return { unit, stepSize }
switch(unit){
case 'millisecond':
stepSize *= 1000
break;
case 'second':
//stepSize *= 1
break;
case 'minute':
stepSize /= 60
break;
case 'hour':
stepSize /= 60*60
break;
case 'day':
stepSize /= 24*60*60
break;
case 'week':
stepSize /= 7*24*60*60
break;
case 'month':
stepSize /= 30*24*60*60
break;
case 'quarter':
stepSize /= 91*24*60*60
break;
case 'year':
stepSize /= 365.25*24*60*60
break;
}
stepSize = Math.round(stepSize/linesPerInterval)
stepSize = stepSize > 0 ? stepSize : 1
return {unit, stepSize}
} }
export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams}) => { export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams}) => {
@ -198,21 +183,22 @@ export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams
const [chart, setChart] = useState<any>() const [chart, setChart] = useState<any>()
useEffect(() => { useEffect(() => {
let thisOptions = {} const chartOptions: ChartOptions = {}
Object.assign(thisOptions, defaultOptions, options) Object.assign(chartOptions, defaultOptions, options)
let newChart = new Chart(chartRef.current ?? "", { const newChart = new Chart(chartRef.current ?? '', {
type: 'line', type: 'line',
plugins: [ChartDataLabels], plugins: [ChartDataLabels],
options: thisOptions, options: chartOptions,
data: { datasets: [] } data: { datasets: [] }
}) })
setChart(newChart) setChart(newChart)
return () => newChart?.destroy() return () => newChart?.destroy()
}, [options]) }, [options])
useEffect(() => { useEffect(() => {
if (!chart) return; if (!chart) return
chart.data = dataParams.data chart.data = dataParams.data
if(dataParams.yStart){ if(dataParams.yStart){
const interval = Number(dataParams.yInterval ?? 600_000) const interval = Number(dataParams.yInterval ?? 600_000)
@ -227,7 +213,7 @@ export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams
} }
} }
chart.update() chart.update(0)
}, [chart, dataParams]) }, [chart, dataParams])
return(<canvas ref={chartRef} />) return(<canvas ref={chartRef} />)

View File

@ -1,7 +1,44 @@
import { ChartOptions, Scriptable, ScriptableContext } from 'chart.js'
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { ChartTimeBase } from './ChartTimeBase' import { makeDateSorter } from '../Table'
import {
ChartTimeBase,
ChartTimeData,
ChartTimeDataset,
ChartTimeDataPoint,
ChartTimeDataParams
} from './ChartTimeBase'
const chartPluginsOptions = { export type ColumnLineConfig = {
label?: string
units?: string
xAccessorName: string
yAccessorName: string
color?: string
showLine?: boolean
isShape?: boolean
xConstValue?: number | string
dash?: Array<number>
borderColor?: string
backgroundColor?: string
borderWidth?: Scriptable<number, ScriptableContext<'radar'>>
fill?: string
}
export type ColumnPostParsing = (data: ChartTimeDataParams) => void
export type ColumnData = { [accessors: string]: any }
export type ColumnAdditionalData = (point: ColumnData, cfg: ColumnLineConfig) => object
export type ColumnProps = {
postParsing?: ColumnPostParsing
additionalPointData?: ColumnAdditionalData
interval?: number
yDisplay?: boolean
yStart?: Date
lineGroup: ColumnLineConfig[]
data: ColumnData[]
}
const chartPluginsOptions: ChartOptions = {
plugins: { plugins: {
datalabels: { datalabels: {
backgroundColor: 'transparent', backgroundColor: 'transparent',
@ -15,16 +52,16 @@ const chartPluginsOptions = {
clip: true clip: true
}, },
legend: { display: false }, legend: { display: false },
tooltip: { enable: true } tooltip: { enabled: true }
} }
} }
const GetRandomColor = () => '#' + Math.floor(Math.random()*(16**6-1)).toString(16) const GetRandomColor = () => '#' + Math.floor(Math.random() * (16**6 - 1)).toString(16)
export const GetOrCreateDatasetByLineConfig = (data, lineConfig) => { export const GetOrCreateDatasetByLineConfig = (data: ChartTimeData, lineConfig: ColumnLineConfig): ChartTimeDataset => {
let dataset = data?.datasets.find(d => d.label === lineConfig.label) let dataset = data?.datasets.find(d => d.label === lineConfig.label)
if(!dataset) { if (!dataset) {
let color = lineConfig.borderColor const color = lineConfig.borderColor
?? lineConfig.backgroundColor ?? lineConfig.backgroundColor
?? lineConfig.color ?? lineConfig.color
?? GetRandomColor() ?? GetRandomColor()
@ -45,8 +82,8 @@ export const GetOrCreateDatasetByLineConfig = (data, lineConfig) => {
return dataset return dataset
} }
export const Column = React.memo(({ lineGroup, data, postParsing, additionalPointData, interval, yDisplay, yStart }) => { export const Column: React.NamedExoticComponent<ColumnProps> = React.memo(({ lineGroup, data, postParsing, additionalPointData, interval, yDisplay, yStart }) => {
const [dataParams, setDataParams] = useState({data: {datasets:[]}, yStart, }) const [dataParams, setDataParams] = useState<ChartTimeDataParams>({data: {datasets:[]}, yStart, })
useEffect(()=>{ useEffect(()=>{
if((lineGroup.length === 0) || (data.length === 0)) return if((lineGroup.length === 0) || (data.length === 0)) return
@ -54,16 +91,17 @@ export const Column = React.memo(({ lineGroup, data, postParsing, additionalPoin
setDataParams((preDataParams) => { setDataParams((preDataParams) => {
lineGroup.forEach(lineCfg => { lineGroup.forEach(lineCfg => {
const dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg) const dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg)
let points = data.map(dataItem => ({ let points: ChartTimeDataPoint[] = data.map(dataItem => ({
x: lineCfg.xConstValue ?? dataItem[lineCfg.xAccessorName], x: lineCfg.xConstValue ?? dataItem[lineCfg.xAccessorName],
label: dataItem[lineCfg.xAccessorName], label: dataItem[lineCfg.xAccessorName],
y: new Date(dataItem[lineCfg.yAccessorName]), y: new Date(dataItem[lineCfg.yAccessorName]),
depth: dataItem.wellDepth,
...additionalPointData?.(dataItem, lineCfg) ...additionalPointData?.(dataItem, lineCfg)
})).filter(point => (point.x ?? null) !== null && (point.y ?? null) !== null) }))
points = points.filter(point => (point.x ?? null) !== null && (point.y ?? null) !== null)
if(points?.length > 2) if(points?.length > 2)
points.sort((a,b) => a.y > b.y ? 1 : -1) points.sort(makeDateSorter('y'))
dataset.data = points dataset.data = points
}) })

View File

@ -4,8 +4,6 @@ import { ChartTimeOnlineFooter } from './ChartTimeOnlineFooter'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { makeDateSorter } from '../../components/Table' import { makeDateSorter } from '../../components/Table'
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 GetLimitShape = (flowChartData, points, accessor) => {
const min = [], max = [] const min = [], max = []
@ -29,12 +27,12 @@ const RemoveSimilar = (input, accessor) => {
return data return data
} }
export const MonitoringColumn = ({ lineGroup, data, flowChartData, interval, showBorder, style, headerHeight, pointCount }) => { export const MonitoringColumn = ({ lineGroup, data, flowChartData, interval, showBorder, style, headerHeight, pointCount = 2048, additionalLabels }) => {
const [dataStore, setDataStore] = useState([]) const [dataStore, setDataStore] = useState([])
const [lineGroupWithoutShapes, setLineGroupWithoutShapes] = useState([]) const [lineGroupWithoutShapes, setLineGroupWithoutShapes] = useState([])
const dataLast = data?.[data.length - 1] const dataLast = data?.[data.length - 1]
const yStart = new Date(+(dataLast?.date ? new Date(dataLast.date) : new Date()) - interval * 0.97) const yStart = new Date((dataLast?.date ? +new Date(dataLast.date) : Date.now()) - interval * 0.97)
const pv = lineGroup.filter(line => line.showLabels).map(line => ({ let pv = lineGroup.filter(line => line.showLabels).map(line => ({
color: line.color, color: line.color,
label: line.label, label: line.label,
unit: line.units, unit: line.units,
@ -80,8 +78,30 @@ export const MonitoringColumn = ({ lineGroup, data, flowChartData, interval, sho
</Grid> </Grid>
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<Grid className={'display_chart_values'}> <Grid className={'display_chart_values'}>
{pv?.map((v, idx) => ( {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> const text = `${v.value?.toFixed(2) ?? '--'} ${v.unit}`
return (
<GridItem
key={idx}
row={idx + 1}
col={1}
className={'monitoring_value'}
style={{ color: v.color, padding: '0 4px' }}
data-before={text}
children={text}
/>
)
})}
{additionalLabels?.map((label, idx) => (
<GridItem
key={idx}
row={(pv?.length ?? 0) + idx + 1}
col={1}
className={'monitoring_value'}
style={{ color: 'black', padding: '0 4px' }}
data-before={label}
children={label}
/>
))} ))}
</Grid> </Grid>
<Column <Column
@ -98,7 +118,3 @@ export const MonitoringColumn = ({ lineGroup, data, flowChartData, interval, sho
</div> </div>
) )
} }
MonitoringColumn.defaultProps = {
pointCount: 2048
}

View File

@ -13,6 +13,7 @@ import { Grid, GridItem, Flex } from '../../components/Grid'
import { Subscribe } from '../../services/signalr' import { Subscribe } from '../../services/signalr'
import { import {
DrillFlowChartService, DrillFlowChartService,
OperationStatService,
TelemetryDataSaubService, TelemetryDataSaubService,
TelemetryDataSpinService, TelemetryDataSpinService,
WellService WellService
@ -306,6 +307,7 @@ export default function TelemetryView({ idWell }) {
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 [rop, setRop] = useState(null)
const handleDataSaub = (data) => { const handleDataSaub = (data) => {
if (data) { if (data) {
@ -345,6 +347,8 @@ export default function TelemetryView({ idWell }) {
useEffect(() => invokeWebApiWrapperAsync( useEffect(() => invokeWebApiWrapperAsync(
async () => { async () => {
const well = await WellService.get(idWell) const well = await WellService.get(idWell)
const rop = await OperationStatService.getClusterRopStatByIdWell(idWell)
setRop(rop)
setWellData(well ?? {}) setWellData(well ?? {})
}, },
setShowLoader, setShowLoader,
@ -403,6 +407,10 @@ export default function TelemetryView({ idWell }) {
interval={chartInterval * 1000} interval={chartInterval * 1000}
headerHeight={'50px'} headerHeight={'50px'}
showBorder={getIndexOfDrillingBy(dataSaub) === index} showBorder={getIndexOfDrillingBy(dataSaub) === index}
additionalLabels={rop && (index === 1) ? [
`ROP сред: ${rop.ropAverage.toFixed(2)} м/ч`,
`ROP макс: ${rop.ropMax.toFixed(2)} м/ч`
] : null}
/> />
</GridItem> </GridItem>
)} )}

View File

@ -24,6 +24,21 @@
font-weight: bold; font-weight: bold;
} }
.monitoring_value {
position: relative;
z-index: 0;
}
.monitoring_value:before {
position: absolute;
z-index: -1;
left: 4px;
top: 0;
-webkit-text-stroke: 4px white;
content: attr(data-before);
}
.display_label{ .display_label{
font-size: 16px; font-size: 16px;
color: rgb(70, 70, 70); color: rgb(70, 70, 70);