diff --git a/src/components/charts/ChartTimeBase.tsx b/src/components/charts/ChartTimeBase.tsx index 596e6a1..8140a67 100644 --- a/src/components/charts/ChartTimeBase.tsx +++ b/src/components/charts/ChartTimeBase.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState} from 'react' +import { useEffect, useRef, useState } from 'react' import { Chart, TimeScale, @@ -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,19 +27,13 @@ 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:{ + y: { type: 'time', reverse: true, time: { @@ -54,7 +50,7 @@ const defaultOptions = { year: 'yyyy.MM', }, }, - grid:{ + grid: { drawTicks: false, }, ticks: { @@ -66,20 +62,20 @@ const defaultOptions = { } }, - x:{ - type:'linear', - position:'top' + x: { + type: 'linear', + position: 'top' } }, parsing: false, - elements:{ - point:{ - radius:0, - hoverRadius:5, + elements: { + point: { + radius: 0, + hoverRadius: 5, }, }, - plugins:{ - legend:{ + plugins: { + legend: { display: false, }, datalabels: { @@ -97,100 +93,89 @@ const defaultOptions = { mode: 'x', } }, + tooltip: { + enabled: true, + callbacks: { + label: (tooltipItem: any) => tooltipItem.yLabel + } + }, } } -export type ChartTimeData = ChartData +export type ChartTimeDataPoint = { + x: number + label: number + y: Date +} + +export type ChartTimeDataset = ChartDataset +export type ChartTimeData = ChartData 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 | 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' + + 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 - return {unit, stepSize} + const unit = timeUnitByInterval(intervalSec) + const stepSize = Math.max(1, Math.round(intervalSec / intervals[unit] / linesPerInterval)) + return { unit, stepSize } } export const ChartTimeBase: React.FC = ({options, dataParams}) => { @@ -198,21 +183,22 @@ export const ChartTimeBase: React.FC = ({options, dataParams const [chart, setChart] = useState() 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 = ({options, dataParams } } - chart.update() + chart.update(0) }, [chart, dataParams]) return() diff --git a/src/components/charts/Column.jsx b/src/components/charts/Column.tsx similarity index 51% rename from src/components/charts/Column.jsx rename to src/components/charts/Column.tsx index 3944dc3..872b2cf 100644 --- a/src/components/charts/Column.jsx +++ b/src/components/charts/Column.tsx @@ -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 + borderColor?: string + backgroundColor?: string + borderWidth?: Scriptable> + 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) +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 + if (!dataset) { + 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 = React.memo(({ lineGroup, data, postParsing, additionalPointData, interval, yDisplay, yStart }) => { + const [dataParams, setDataParams] = useState({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 }) diff --git a/src/pages/TelemetryView/MonitoringColumn.jsx b/src/pages/TelemetryView/MonitoringColumn.jsx index 906bf36..4c84805 100644 --- a/src/pages/TelemetryView/MonitoringColumn.jsx +++ b/src/pages/TelemetryView/MonitoringColumn.jsx @@ -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
- {pv?.map((v, idx) => ( - {v.value?.toFixed(2) ?? '--'} {v.unit} + {pv?.map((v, idx) => { + const text = `${v.value?.toFixed(2) ?? '--'} ${v.unit}` + return ( + + ) + })} + {additionalLabels?.map((label, idx) => ( + ))} ) } - -MonitoringColumn.defaultProps = { - pointCount: 2048 -} diff --git a/src/pages/TelemetryView/index.jsx b/src/pages/TelemetryView/index.jsx index 529156e..0be2bcf 100644 --- a/src/pages/TelemetryView/index.jsx +++ b/src/pages/TelemetryView/index.jsx @@ -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} /> )} diff --git a/src/styles/display.css b/src/styles/display.css index 9058382..afd40da 100644 --- a/src/styles/display.css +++ b/src/styles/display.css @@ -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);