Отображение 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

@ -8,8 +8,10 @@ import {
PointElement,
LineElement,
ChartData,
ChartTypeRegistry,
ChartOptions} from 'chart.js'
ChartOptions,
ChartType,
ChartDataset
} from 'chart.js'
import 'chartjs-adapter-moment'
import ChartDataLabels from 'chartjs-plugin-datalabels'
import zoomPlugin from 'chartjs-plugin-zoom'
@ -25,16 +27,10 @@ Chart.register(
zoomPlugin
)
const defaultOptions = {
const defaultOptions: ChartOptions = {
responsive: true,
aspectRatio: 0.45,
animation: false,
tooltips: {
enabled: true,
callbacks: {
label: (tooltipItem:any) => tooltipItem.yLabel
}
},
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
scales: {
y: {
@ -97,99 +93,88 @@ const defaultOptions = {
mode: 'x',
}
},
tooltip: {
enabled: true,
callbacks: {
label: (tooltipItem: any) => tooltipItem.yLabel
}
},
}
}
export type ChartTimeData = ChartData<keyof ChartTypeRegistry, {
x: number;
label: number;
y: Date;
}[], unknown>
export type ChartTimeDataPoint = {
x: number
label: number
y: Date
}
export type ChartTimeDataset = ChartDataset<ChartType, ChartTimeDataPoint[]>
export type ChartTimeData = ChartData<ChartType, ChartTimeDataPoint[], unknown>
export type ChartTimeDataParams = {
data: ChartTimeData,
yStart?: Date,
yInterval?: number,
displayLabels?: Boolean,
data: ChartTimeData
yStart?: Date
yInterval?: number
displayLabels?: boolean
}
export type ChartTimeBaseProps = {
dataParams: ChartTimeDataParams,
// TODO: Create good type for options
options?: ChartOptions<keyof ChartTypeRegistry> | any,
dataParams: ChartTimeDataParams
options?: ChartOptions
}
export type TimeParams = {
unit: String
unit: string
stepSize: number
}
const linesPerInterval = 32
export const timeUnitByInterval = (intervalSec: number): String => {
if(intervalSec <= 60)
const intervals = {
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'
if(intervalSec <= 32*60)
if(intervalSec <= 32 * intervals.minute)
return 'second'
if(intervalSec <= 32*60*60)
if(intervalSec <= 32 * intervals.hour)
return 'minute'
if(intervalSec <= 32*12*60*60)
if(intervalSec <= 32 * intervals.day / 2)
return 'hour'
if(intervalSec <= 32*24*60*60)
if(intervalSec <= 32 * intervals.day)
return 'day'
if(intervalSec <= 32*7*24*60*60)
if(intervalSec <= 32 * intervals.week)
return 'week'
if(intervalSec <= 32*30.4375*24*60*60)
if(intervalSec <= 32 * intervals.year / 12)
return 'month'
if(intervalSec <= 32*121.75*24*60*60)
if(intervalSec <= 32 * intervals.year / 3)
return 'quarter'
else
return 'year'
}
export const timeParamsByInterval = (intervalSec: number): TimeParams => {
let stepSize = intervalSec
let unit = timeUnitByInterval(intervalSec)
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
const unit = timeUnitByInterval(intervalSec)
const stepSize = Math.max(1, Math.round(intervalSec / intervals[unit] / linesPerInterval))
return { unit, stepSize }
}
@ -198,21 +183,22 @@ export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams
const [chart, setChart] = useState<any>()
useEffect(() => {
let thisOptions = {}
Object.assign(thisOptions, defaultOptions, options)
const chartOptions: ChartOptions = {}
Object.assign(chartOptions, defaultOptions, options)
let newChart = new Chart(chartRef.current ?? "", {
const newChart = new Chart(chartRef.current ?? '', {
type: 'line',
plugins: [ChartDataLabels],
options: thisOptions,
options: chartOptions,
data: { datasets: [] }
})
setChart(newChart)
return () => newChart?.destroy()
}, [options])
useEffect(() => {
if (!chart) return;
if (!chart) return
chart.data = dataParams.data
if(dataParams.yStart){
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])
return(<canvas ref={chartRef} />)

View File

@ -1,7 +1,44 @@
import { ChartOptions, Scriptable, ScriptableContext } from 'chart.js'
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: {
datalabels: {
backgroundColor: 'transparent',
@ -15,16 +52,16 @@ const chartPluginsOptions = {
clip: true
},
legend: { display: false },
tooltip: { enable: true }
tooltip: { enabled: true }
}
}
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)
if (!dataset) {
let color = lineConfig.borderColor
const color = lineConfig.borderColor
?? lineConfig.backgroundColor
?? lineConfig.color
?? GetRandomColor()
@ -45,8 +82,8 @@ export const GetOrCreateDatasetByLineConfig = (data, lineConfig) => {
return dataset
}
export const Column = React.memo(({ lineGroup, data, postParsing, additionalPointData, interval, yDisplay, yStart }) => {
const [dataParams, setDataParams] = useState({data: {datasets:[]}, yStart, })
export const Column: React.NamedExoticComponent<ColumnProps> = React.memo(({ lineGroup, data, postParsing, additionalPointData, interval, yDisplay, yStart }) => {
const [dataParams, setDataParams] = useState<ChartTimeDataParams>({data: {datasets:[]}, yStart, })
useEffect(()=>{
if((lineGroup.length === 0) || (data.length === 0)) return
@ -54,16 +91,17 @@ export const Column = React.memo(({ lineGroup, data, postParsing, additionalPoin
setDataParams((preDataParams) => {
lineGroup.forEach(lineCfg => {
const dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg)
let points = data.map(dataItem => ({
let points: ChartTimeDataPoint[] = 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)
}))
points = points.filter(point => (point.x ?? null) !== null && (point.y ?? null) !== null)
if(points?.length > 2)
points.sort((a,b) => a.y > b.y ? 1 : -1)
points.sort(makeDateSorter('y'))
dataset.data = points
})

View File

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

View File

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

View File

@ -24,6 +24,21 @@
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{
font-size: 16px;
color: rgb(70, 70, 70);