Merged analytics into master

This commit is contained in:
Aleksey Nokhrin 2021-07-27 06:36:40 +00:00
commit f50855d179
24 changed files with 22684 additions and 76 deletions

View File

@ -1,5 +1,6 @@
{
"cSpell.words": [
"день"
]
],
"liveServer.settings.port": 5501
}

21354
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@
"chart.js": "^3.3.0",
"chartjs-adapter-moment": "^1.0.0",
"chartjs-plugin-datalabels": "^2.0.0-rc.1",
"chartjs-plugin-zoom": "^1.1.1",
"craco-less": "^1.17.1",
"moment": "^2.29.1",
"pigeon-maps": "^0.19.7",

View File

@ -0,0 +1,41 @@
import { ChartDepthToDay } from './charts/ChartDepthToDay'
import { useParams } from "react-router-dom"
import notify from "../components/notify"
import { useState, useEffect } from 'react'
import { AnalyticsService } from "../services/api"
const lines = [
{ label: "Глубина забоя", yAccessorName: "wellDepth", color: '#f00' },
{ label: "Положение инструмента", yAccessorName: "bitDepth", color: '#ff0' }
]
export function AnalysisDepthToDay() {
let { id } = useParams()
const [depthToDayData, setDepthToDayData] = useState([])
const [loader, setLoader] = useState(false)
const handleReceiveDepthToDayData = (data) => {
setDepthToDayData(data)
}
useEffect(() => {
setLoader(true)
AnalyticsService.getWellDepthToDay(id)
.then(handleReceiveDepthToDayData)
.catch(error => {
notify(`Не удалось получить данные для Анализа Глубина-День по скважине "${id}"`,
'warning')
console.log(error)
})
.finally(setLoader(false))
}, [id])
return (
<>
<ChartDepthToDay
data={depthToDayData}
lines={lines}
/>
</>
)
}

View File

@ -0,0 +1,61 @@
import { useParams } from "react-router-dom"
import { DatePicker } from 'antd'
import notify from "../components/notify"
import { useState, useEffect } from 'react'
import { AnalyticsService } from '../services/api'
import { ChartDepthToInterval } from './charts/ChartDepthToInterval'
import { Select } from 'antd'
const { Option } = Select
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 часа' }
]
const { RangePicker } = DatePicker
const lines = [{ label: 'График скорость проходки-интервал', yAccessorName: "intervalDepthProgress", xAccessorName: "intervalStartDate", color: '#00f' }]
export function AnalysisDepthToInterval() {
let { id } = useParams()
const [depthToIntervalData, setDepthToIntervalData] = useState([])
const [loader, setLoader] = useState(false)
const [chartInterval, setChartInterval] = useState(600)
const children = timePeriodCollection.map((line) => <Option key={line.value}>{line.label}</Option>)
const handleReceiveDepthToIntervalData = (data) => {
setDepthToIntervalData(data)
}
useEffect(() => {
setLoader(true)
AnalyticsService.getWellDepthToInterval(id, chartInterval)
.then(handleReceiveDepthToIntervalData)
.catch(error => {
notify(`Не удалось получить данные для Анализа скорость проходки-интервал "${id}"`,
'warning')
console.log(error)
})
.finally(setLoader(false))
}, [id, chartInterval])
return (
<>
<Select defaultValue="600" onChange={setChartInterval}>
{children}
</Select>
<ChartDepthToInterval
data={depthToIntervalData}
lines={lines}
/>
</>
)
}

View File

@ -0,0 +1,60 @@
import { useParams } from "react-router-dom"
import { DatePicker, ConfigProvider } from 'antd';
import notify from "../components/notify"
import { useState, useEffect } from 'react'
import { AnalyticsService } from '../services/api'
import { ChartOperationTime } from './charts/ChartOperationTime'
import locale from "antd/lib/locale/ru_RU";
const { RangePicker } = DatePicker
const lines = [{ labelAccessorName: "processName", pieceAccessorName: "duration" }]
export function AnalysisOperationTime() {
let { id } = useParams()
const [operationTimeData, setOperationTimeData] = useState([])
const [loader, setLoader] = useState(false)
const [range, setRange] = useState([])
let begin = null
let end = null
const onChangeRange = (range) => {
setRange(range)
}
const handleReceiveOperationTimeData = (data) => {
setOperationTimeData(data)
}
useEffect(() => {
setLoader(true)
if (range?.length > 1) {
begin = range[0].toISOString()
end = range[1].toISOString()
}
AnalyticsService.getOperationsSummary(id, begin, end)
.then(handleReceiveOperationTimeData)
.catch(error => {
notify(`Не удалось получить данные для Анализа Операция-Время по скважине "${id}" за период с ${begin} по ${end}`,
'warning')
console.log(error)
})
.finally(setLoader(false))
}, [id, range])
return (
<>
<ConfigProvider locale={locale}>
<RangePicker
showTime
onChange={onChangeRange}
/>
</ConfigProvider>
<ChartOperationTime
data={operationTimeData}
lines={lines}
/>
</>
)
}

View File

@ -0,0 +1,38 @@
import { useEffect, useState } from 'react'
import { ChartDepthToDayBase } from './ChartDepthToDayBase'
import { CreateDataset } from './ChartTimeArchive'
export const ChartDepthToDay = ({data, lines}) => {
const [depthToDayDataParams, setDepthToDayDataParams] = useState({data: {datasets: []}})
useEffect(() => {
if ((!lines)
|| (!data))
return
let newDatasets = lines.map(lineCfg => {
let datasets = CreateDataset(lineCfg)
if(data.length !== 0)
datasets.data = data.map(dataItem => {
return {
x: new Date(dataItem[lineCfg.xAccessorName??'date']),
y: dataItem[lineCfg.yAccessorName],
label: dataItem[lineCfg.label]
}
})
return datasets
})
let newParams = {
displayLabels: true,
data: {
datasets: newDatasets
}
}
setDepthToDayDataParams(newParams)
}, [data, lines])
return (
<ChartDepthToDayBase dataParams={depthToDayDataParams} />
)
}

View File

@ -0,0 +1,224 @@
import {useEffect, useRef, useState} from 'react';
import {
Chart,
TimeScale,
LinearScale,
Legend,
LineController,
PointElement,
LineElement,
ChartData,
ChartTypeRegistry,
ChartOptions
} from 'chart.js'
import 'chartjs-adapter-moment';
import ChartDataLabels from 'chartjs-plugin-datalabels';
import zoomPlugin from 'chartjs-plugin-zoom';
Chart.register(TimeScale, LinearScale, LineController, LineElement, PointElement, Legend, ChartDataLabels, zoomPlugin);
const defaultOptions = {
responsive: true,
aspectRatio: 6,
animation: false,
tooltips: {
enabled: true,
callbacks: {
label(tooltipItem:any) {
return tooltipItem.yLabel;
}
}
},
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
scales: {
x:{
type: 'time',
reverse: false,
time: {
stepSize: 20,
displayFormats: {
millisecond: 'HH:mm:ss.SSS',
second: 'HH:mm:ss',
minute: 'HH:mm:ss',
hour: 'DD HH:mm:ss',
day: 'MM.DD HH:mm',
week: 'yy.MM.DD HH:mm',
month: 'yyyy.MM.DD',
quarter: 'yyyy.MM.DD',
year: 'yyyy.MM',
},
},
grid:{
drawTicks: false,
},
ticks: {
z: 1,
display : true,
textStrokeColor : "#fff",
textStrokeWidth : 2,
color:"#000",
}
},
y:{
type:'linear',
position:'top'
}
},
elements:{
point:{
radius:0,
hoverRadius:5,
},
},
plugins:{
legend:{
display: true,
},
datalabels: {
display: false,
},
}
}
export type ChartTimeData = ChartData<keyof ChartTypeRegistry, {
x: Date;
label: number;
y: number;
}[], unknown>
export type ChartTimeDataParams = {
data: ChartTimeData,
xStart?: Date,
xInterval?: number,
displayLabels?: Boolean,
}
export type ChartTimeBaseProps = {
dataParams: ChartTimeDataParams,
// TODO: Create good type for options
options?: ChartOptions<keyof ChartTypeRegistry> | any,
}
export type TimeParams = {
unit: String
stepSize: number
}
const linesPerInterval = 32
export const timeUnitByInterval = (intervalSec:number):String => {
if(intervalSec <= 60)
return 'millisecond'
if(intervalSec <= 32*60)
return 'second'
if(intervalSec <= 32*60*60)
return 'minute'
if(intervalSec <= 32*12*60*60)
return 'hour'
if(intervalSec <= 32*24*60*60)
return 'day'
if(intervalSec <= 32*7*24*60*60)
return 'week'
if(intervalSec <= 32*30.4375*24*60*60)
return 'month'
if(intervalSec <= 32*121.75*24*60*60)
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;
return {unit, stepSize}
}
export const ChartDepthToDayBase: React.FC<ChartTimeBaseProps> = ({options, dataParams}) => {
const chartRef = useRef<HTMLCanvasElement>(null)
const [chart, setChart] = useState<any>()
useEffect(() => {
if((chartRef.current)&&(!chart)) {
let thisOptions = {}
Object.assign(thisOptions, defaultOptions, options)
let newChart = new Chart(chartRef.current, {
type: 'line',
plugins: [ChartDataLabels],
options: thisOptions,
data: dataParams.data
})
setChart(newChart)
return () => chart?.destroy()
}
}, [chart, options, dataParams])
useEffect(() => {
if (!chart)
return
chart.data = dataParams.data
chart.options.aspectRatio = options?.aspectRatio
if (dataParams.xStart) {
let interval = Number(dataParams.xInterval ?? 600)
let start = new Date(dataParams.xStart)
let end = new Date(dataParams.xStart)
end.setSeconds(end.getSeconds() + interval)
let {unit, stepSize} = timeParamsByInterval(interval)
if(chart.options.scales?.x){
chart.options.scales.x.max = end.getTime()
chart.options.scales.x.min = start.getTime()
chart.options.scales.x.ticks.display = dataParams.displayLabels ?? true
chart.options.scales.x.time.unit = unit
chart.options.scales.x.time.stepSize = stepSize
}
}
chart.update()
}, [chart, dataParams, options])
return(<canvas ref={chartRef} />)
}

View File

@ -0,0 +1,39 @@
import { useEffect, useState } from 'react'
import { ChartDepthToIntervalBase } from './ChartDepthToIntervalBase'
import { CreateDataset } from './ChartTimeArchive'
export const ChartDepthToInterval = ({ lines, data }) => {
const [depthToIntervalDataParams, setDepthToIntervalDataParams] = useState({ data: { datasets: [] } })
useEffect(() => {
if ((!lines)
|| (!data))
return
let newDatasets = lines.map(lineCfg => {
let datasets = CreateDataset(lineCfg)
if (data.length !== 0)
datasets.data = data.map(dataItem => {
return {
x: new Date(dataItem[lineCfg.xAccessorName ?? 'intervalStartDate']),
y: dataItem[lineCfg.yAccessorName ?? 'intervalDepthProgress'],
}
})
return datasets
})
let newParams = {
displayLabels: true,
data: {
datasets: newDatasets
}
}
setDepthToIntervalDataParams(newParams)
}, [data, lines])
return (<>
<ChartDepthToIntervalBase dataParams={depthToIntervalDataParams} />
</>
)
}

View File

@ -0,0 +1,224 @@
import { useEffect, useRef, useState } from 'react'
import {
Chart,
TimeScale,
Legend,
PointElement,
ChartData,
ChartTypeRegistry,
ChartOptions,
BarController,
BarElement,
TimeSeriesScale,
LinearScale,
LineController,
} from 'chart.js'
import 'chartjs-adapter-moment'
import ChartDataLabels from 'chartjs-plugin-datalabels'
Chart.register(TimeScale, BarController, BarElement, PointElement, TimeSeriesScale, LineController, LinearScale, Legend, ChartDataLabels)
const defaultOptions = {
responsive: true,
aspectRatio: 4,
animation: false,
tooltips: {
enabled: true,
callbacks: {
label(tooltipItem: any) {
return tooltipItem.yLabel;
}
}
},
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
scales: {
x: {
position: 'bottom',
type: 'time',
reverse: false,
time: {
stepSize: 20,
displayFormats: {
millisecond: 'HH:mm:ss.SSS',
second: 'HH:mm:ss',
minute: 'HH:mm:ss',
hour: 'DD HH:mm:ss',
day: 'MM.DD HH:mm',
week: 'yy.MM.DD HH:mm',
month: 'yyyy.MM.DD',
quarter: 'yyyy.MM.DD',
year: 'yyyy.MM',
},
},
grid: {
drawTicks: false,
},
ticks: {
z: 1,
display: true,
textStrokeColor: "#ffff",
textStrokeWidth: 2,
color: "#000",
}
},
y: {
beginAtZero: true,
}
},
elements: {
point: {
radius: 0,
hoverRadius: 5,
},
},
plugins: {
legend: {
display: true,
},
datalabels: {
display: false,
},
}
}
export type ChartTimeData = ChartData<keyof ChartTypeRegistry, {
x: Date;
label: number;
y: number;
}[], unknown>
export type ChartTimeDataParams = {
data: ChartTimeData,
xStart?: Date,
xInterval?: number,
displayLabels?: Boolean,
}
export type ChartTimeBaseProps = {
dataParams: ChartTimeDataParams,
// TODO: Create good type for options
options?: ChartOptions<keyof ChartTypeRegistry> | any,
}
export type TimeParams = {
unit: String
stepSize: number
}
const linesPerInterval = 32
export const timeUnitByInterval = (intervalSec: number): String => {
if (intervalSec <= 60)
return 'millisecond'
if (intervalSec <= 32 * 60)
return 'second'
if (intervalSec <= 32 * 60 * 60)
return 'minute'
if (intervalSec <= 32 * 12 * 60 * 60)
return 'hour'
if (intervalSec <= 32 * 24 * 60 * 60)
return 'day'
if (intervalSec <= 32 * 7 * 24 * 60 * 60)
return 'week'
if (intervalSec <= 32 * 30.4375 * 24 * 60 * 60)
return 'month'
if (intervalSec <= 32 * 121.75 * 24 * 60 * 60)
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;
return { unit, stepSize }
}
export const ChartDepthToIntervalBase: React.FC<ChartTimeBaseProps> = ({ options, dataParams }) => {
const chartRef = useRef<HTMLCanvasElement>(null)
const [chart, setChart] = useState<any>()
useEffect(() => {
if ((chartRef.current) && (!chart)) {
let thisOptions = {}
Object.assign(thisOptions, defaultOptions, options)
let newChart = new Chart(chartRef.current, {
type: 'bar',
plugins: [ChartDataLabels],
options: thisOptions,
data: dataParams.data
})
setChart(newChart)
return () => chart?.destroy()
}
}, [chart, options, dataParams])
useEffect(() => {
if (!chart)
return
chart.data = dataParams.data
chart.options.aspectRatio = options?.aspectRatio
if (dataParams.xStart) {
let interval = Number(dataParams.xInterval ?? 600)
let start = new Date(dataParams.xStart)
let end = new Date(dataParams.xStart)
end.setSeconds(end.getSeconds() + interval)
let { unit, stepSize } = timeParamsByInterval(interval)
if (chart.options.scales?.x) {
chart.options.scales.x.max = end.getTime()
chart.options.scales.x.min = start.getTime()
chart.options.scales.x.ticks.display = dataParams.displayLabels ?? true
chart.options.scales.x.time.unit = unit
chart.options.scales.x.time.stepSize = stepSize
}
}
chart.update()
}, [chart, dataParams, options])
return (<canvas height='100%' width='100%' ref={chartRef} />)
}

View File

@ -0,0 +1,67 @@
import { useEffect, useState } from 'react'
import { ChartOpertationTimeBase } from './ChartOperationTimeBase'
import moment from 'moment'
export const CreateLabels = () => {
let labels = []
return labels
}
export const CreateData = (lineConfig) => {
let datasets = {
label: lineConfig.label,
data: [],
backgroundColor: [
'#f00', '#ff0', '#f0f', '#0ff', '#00f', '#0f0'
],
}
return datasets
}
export const ChartOperationTime = ({ lines, data, rangeDate }) => {
const [opertationTimeDataParams, setOpertationTimeDataParams] = useState({ data: { labels: [], datasets: [] } })
useEffect(() => {
if ((!lines)
|| (!data))
return
let newLabels = lines.map(lineCfg => {
let labels = CreateLabels(lineCfg)
if (data.length !== 0)
labels = data.map(dataItem => {
return dataItem[lineCfg.labelAccessorName]
})
return labels
})
let newDatasets = lines.map(lineCfg => {
let datasets = CreateData(lineCfg)
if (data.length !== 0)
datasets.data = data.map(dataItem => {
return dataItem[lineCfg.pieceAccessorName]
})
return datasets
})
let interval = rangeDate ? (rangeDate[1] - rangeDate[0]) / 1000 : null
let startDate = rangeDate ? rangeDate[0] : moment()
let newParams = {
xInterval: interval,
xStart: startDate,
data: {
labels: newLabels,
datasets: newDatasets
}
}
setOpertationTimeDataParams(newParams)
console.log(newParams)
}, [data, lines, rangeDate])
return (<>
<ChartOpertationTimeBase dataParams={opertationTimeDataParams} />
</>
)
}

View File

@ -0,0 +1,177 @@
import { useEffect, useRef, useState } from 'react'
import {
Chart,
ArcElement,
TimeScale,
Legend,
PointElement,
ChartData,
ChartTypeRegistry,
ChartOptions,
DoughnutController,
} from 'chart.js'
import 'chartjs-adapter-moment'
import ChartDataLabels from 'chartjs-plugin-datalabels'
Chart.register(TimeScale, DoughnutController, PointElement, ArcElement, Legend, ChartDataLabels)
const defaultOptions = {
responsive: true,
title: {
display: true,
position: "top",
text: "Doughnut Chart",
fontSize: 18,
fontColor: "#111"
},
legend: {
display: true,
position: "bottom",
labels: {
fontColor: "#333",
fontSize: 16
}
}
}
export type ChartTimeData = ChartData<keyof ChartTypeRegistry, {
x: String;
label: number;
y: number;
}[], unknown>
export type ChartTimeDataParams = {
data: ChartTimeData,
xStart?: Date,
xInterval?: number,
displayLabels?: Boolean,
}
export type ChartTimeBaseProps = {
dataParams: ChartTimeDataParams,
// TODO: Create good type for options
options?: ChartOptions<keyof ChartTypeRegistry> | any,
}
export type TimeParams = {
unit: String
stepSize: number
}
const linesPerInterval = 32
export const timeUnitByInterval = (intervalSec: number): String => {
if (intervalSec <= 60)
return 'millisecond'
if (intervalSec <= 32 * 60)
return 'second'
if (intervalSec <= 32 * 60 * 60)
return 'minute'
if (intervalSec <= 32 * 12 * 60 * 60)
return 'hour'
if (intervalSec <= 32 * 24 * 60 * 60)
return 'day'
if (intervalSec <= 32 * 7 * 24 * 60 * 60)
return 'week'
if (intervalSec <= 32 * 30.4375 * 24 * 60 * 60)
return 'month'
if (intervalSec <= 32 * 121.75 * 24 * 60 * 60)
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;
return { unit, stepSize }
}
export const ChartOpertationTimeBase: React.FC<ChartTimeBaseProps> = ({ options, dataParams }) => {
const chartRef = useRef<HTMLCanvasElement>(null)
const [chart, setChart] = useState<any>()
useEffect(() => {
if ((chartRef.current) && (!chart)) {
let thisOptions = {}
Object.assign(thisOptions, defaultOptions, options)
let newChart = new Chart(chartRef.current, {
type: 'doughnut',
plugins: [ChartDataLabels],
options: thisOptions,
data: dataParams.data
})
setChart(newChart)
return () => chart?.destroy()
}
}, [chart, options, dataParams])
useEffect(() => {
if (!chart)
return
chart.data = dataParams.data
chart.options.aspectRatio = options?.aspectRatio
if (dataParams.xStart) {
let interval = Number(dataParams.xInterval ?? 600)
let start = new Date(dataParams.xStart)
let end = new Date(dataParams.xStart)
end.setSeconds(end.getSeconds() + interval)
let { unit, stepSize } = timeParamsByInterval(interval)
if (chart.options.scales?.x) {
chart.options.scales.x.max = end.getTime()
chart.options.scales.x.min = start.getTime()
chart.options.scales.x.ticks.display = dataParams.displayLabels ?? true
chart.options.scales.x.time.unit = unit
chart.options.scales.x.time.stepSize = stepSize
}
}
chart.update()
}, [chart, dataParams, options])
return (<canvas ref={chartRef} />)
}

View File

@ -2,15 +2,15 @@ import moment from 'moment';
import { useEffect, useState} from 'react';
import {ChartTimeBase} from './ChartTimeBase'
const GetRandomColor = () => "#" + Math.floor(Math.random()*16777215).toString(16)
export const GetRandomColor = () => "#" + Math.floor(Math.random()*16777215).toString(16)
const CreateDataset = (lineConfig) => {
export const CreateDataset = (lineConfig) => {
let color = lineConfig.borderColor
?? lineConfig.backgroundColor
?? lineConfig.color
?? GetRandomColor()
let dataset = {
let datasets = {
label: lineConfig.label,
data: [],
backgroundColor: lineConfig.backgroundColor ?? color,
@ -18,7 +18,7 @@ const CreateDataset = (lineConfig) => {
borderWidth: lineConfig.borderWidth ?? 1,
borderDash: lineConfig.dash ?? [],
}
return dataset
return datasets
}
const ChartOptions = {
@ -28,14 +28,14 @@ const ChartOptions = {
// display: false,
// maxHeight: 64,
// fullSize: true,
// posision:'chartArea',
// position: 'chartArea',
// align: 'start',
// }
// }
}
export const ChartTimeArchive = ({lines, data, yDisplay, rangeDate, chartRatio}) => {
const [dataParams, setDataParams] = useState({data:{datasets:[]}})
const [dataParams, setDataParams] = useState({data:{datasets: []}})
useEffect(() => {
if ((!lines)
@ -70,4 +70,4 @@ export const ChartTimeArchive = ({lines, data, yDisplay, rangeDate, chartRatio})
opt.aspectRatio = chartRatio
return (<ChartTimeBase dataParams={dataParams} options={opt}/>)
}
}

View File

@ -12,13 +12,22 @@ import {
ChartOptions} from 'chart.js'
import 'chartjs-adapter-moment';
import ChartDataLabels from 'chartjs-plugin-datalabels';
import zoomPlugin from 'chartjs-plugin-zoom';
Chart.register( TimeScale, LinearScale, LineController, LineElement, PointElement, Legend, ChartDataLabels );
Chart.register( TimeScale, LinearScale, LineController, LineElement, PointElement, Legend, ChartDataLabels, zoomPlugin );
const defaultOptions = {
responsive: true,
aspectRatio: 0.45,
animation: false,
animation: false,
tooltips: {
enabled: true,
callbacks: {
label(tooltipItem:any) {
return tooltipItem.yLabel;
}
}
},
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
scales: {
y:{
@ -68,6 +77,18 @@ const defaultOptions = {
datalabels: {
display: false,
},
zoom: {
zoom: {
wheel: {
enabled: true,
modifierKey: 'alt',
},
pinch: {
enabled: true
},
mode: 'x',
}
},
}
}
@ -97,7 +118,7 @@ export type TimeParams = {
const linesPerInterval = 32
const timeUnitByInterval = (intervalSec:number):String => {
export const timeUnitByInterval = (intervalSec:number):String => {
if(intervalSec <= 60)
return 'millisecond'
@ -125,7 +146,7 @@ const timeUnitByInterval = (intervalSec:number):String => {
return 'year'
}
const timeParamsByInterval = (intervalSec:number) :TimeParams => {
export const timeParamsByInterval = (intervalSec:number) :TimeParams => {
let stepSize = intervalSec
let unit = timeUnitByInterval(intervalSec)

View File

@ -2,7 +2,6 @@ import { useEffect, useState} from 'react';
import {ChartTimeBase, ChartTimeData, ChartTimeDataParams} from './ChartTimeBase'
const GetRandomColor = () => "#" + Math.floor(Math.random()*16777215).toString(16)
function GetOrCreateDatasetByLineConfig (data: ChartTimeData, lineConfig: LineConfig) {
let dataset = data?.datasets.find(d=>d.label === lineConfig.label)
if(!dataset)
@ -110,6 +109,9 @@ export const ChartTimeOnline: React.FC<ChartTimeProps> = (props) => {
align: 'left',
anchor: 'center',
clip: true
},
tooltip: {
enable: true
}
}
}

View File

@ -0,0 +1,44 @@
import { ChartOperationsToInterval } from './charts/ChartOperationsToInterval'
import { useParams } from "react-router-dom"
import notify from "../components/notify"
import { useState, useEffect } from 'react'
import { AnalyticsService } from "../services/api"
const lines = [
{ yAccessorName: "operationName", xAccessorName: "duration" },
]
export function AnalysisOperationsToInterval() {
let { id } = useParams()
const [operationsToInterval, setOperationsToInterval] = useState([])
const [loader, setLoader] = useState(false)
const handleReceiveOperationsToIntervalData = (data) => {
setOperationsToInterval(data[0].operations)
console.log(data)
}
useEffect(() => {
setLoader(true)
let intervalHoursTimestamp = 600
let workBeginTimestamp = 1
AnalyticsService.getOperationsToInterval(id, intervalHoursTimestamp, workBeginTimestamp)
.then(handleReceiveOperationsToIntervalData)
.catch(error => {
notify(`Не удалось получить данные для Анализа Глубина-День по скважине "${id}"`,
'warning')
console.log(error)
})
.finally(setLoader(false))
}, [id])
return (
<>
<ChartOperationsToInterval
data={operationsToInterval}
lines={lines}
/>
</>
)
}

View File

@ -0,0 +1,83 @@
import { ChartDepthToIntervalBase } from './ChartOperationsToIntervalBase'
import { useEffect, useState } from 'react'
import { GetRandomColor, CreateDataset } from './ChartTimeArchive'
const CreateLabels = () => {
let labels = []
return labels
}
const CreateData = (lineConfig) => {
let datasets = {
label: lineConfig.label,
data: [],
backgroundColor: GetRandomColor()
}
return datasets
}
const labels = ['Первый']
const datasets = [
{
label: 'Dataset 1',
data: [123],
backgroundColor: "#00f",
},
{
label: 'Dataset 2',
data: [170],
backgroundColor: "#f0f",
},
{
label: 'Dataset 3',
data: [150],
backgroundColor: "#0f0",
}
]
export const ChartOperationsToInterval = ({ data, lines }) => {
const [operationsToIntervalDataParams, setOperationsToIntervalDataParams] = useState({data: {labels: labels, datasets: datasets}})
useEffect(() => {
// if ((!lines)
// || (!data))
// return
let newDatasets = lines.map(lineCfg => {
let datasets = CreateData(lineCfg)
if (data.length !== 0)
datasets = data.map(dataItem => {
return {
label: 'Dataset 1',
data: [dataItem[lineCfg.xAccessorName]],
backgroundColor: "#00f",
}
})
return datasets
})
// let newLabels = lines.map(lineCfg => {
// let labels = CreateLabels(lineCfg)
// if (data.length !== 0)
// labels = data.map(dataItem => {
// return dataItem[lineCfg.yAccessorName]
// })
// return labels
// })
let newParams = {
data: {
datasets: newDatasets
}
}
setOperationsToIntervalDataParams(newParams)
}, [data, lines])
return (<>
<ChartDepthToIntervalBase dataParams={operationsToIntervalDataParams} />
</>
)
}

View File

@ -0,0 +1,180 @@
import { useEffect, useRef, useState } from 'react'
import {
Chart,
TimeScale,
Legend,
PointElement,
ChartData,
ChartTypeRegistry,
ChartOptions,
BarController,
BarElement,
TimeSeriesScale,
LinearScale,
LineController,
CategoryScale
} from 'chart.js'
import 'chartjs-adapter-moment'
import ChartDataLabels from 'chartjs-plugin-datalabels'
Chart.register(TimeScale, BarController, BarElement, PointElement, TimeSeriesScale, LineController, LinearScale, CategoryScale, Legend, ChartDataLabels)
const defaultOptions = {
plugins: {
title: {
display: true,
text: 'Chart.js Bar Chart - Stacked'
},
},
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true
}
}
}
export type ChartTimeData = ChartData<keyof ChartTypeRegistry, {
x: string;
label: string;
y: number;
}[], unknown>
export type ChartTimeDataParams = {
data: ChartTimeData,
xStart?: 0,
xInterval?: number,
displayLabels?: Boolean,
}
export type ChartTimeBaseProps = {
dataParams: ChartTimeDataParams,
// TODO: Create good type for options
options?: ChartOptions<keyof ChartTypeRegistry> | any,
}
export type TimeParams = {
unit: String
stepSize: number
}
const linesPerInterval = 32
export const timeUnitByInterval = (intervalSec: number): String => {
if (intervalSec <= 60)
return 'millisecond'
if (intervalSec <= 32 * 60)
return 'second'
if (intervalSec <= 32 * 60 * 60)
return 'minute'
if (intervalSec <= 32 * 12 * 60 * 60)
return 'hour'
if (intervalSec <= 32 * 24 * 60 * 60)
return 'day'
if (intervalSec <= 32 * 7 * 24 * 60 * 60)
return 'week'
if (intervalSec <= 32 * 30.4375 * 24 * 60 * 60)
return 'month'
if (intervalSec <= 32 * 121.75 * 24 * 60 * 60)
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;
return { unit, stepSize }
}
export const ChartDepthToIntervalBase: React.FC<ChartTimeBaseProps> = ({ options, dataParams }) => {
const chartRef = useRef<HTMLCanvasElement>(null)
const [chart, setChart] = useState<any>()
useEffect(() => {
if ((chartRef.current) && (!chart)) {
let thisOptions = {}
Object.assign(thisOptions, defaultOptions, options)
let newChart = new Chart(chartRef.current, {
type: 'bar',
plugins: [ChartDataLabels],
options: thisOptions,
data: dataParams.data
})
setChart(newChart)
return () => chart?.destroy()
}
}, [chart, options, dataParams])
useEffect(() => {
if (!chart)
return
chart.data = dataParams.data
chart.options.aspectRatio = options?.aspectRatio
if (dataParams.xStart) {
let interval = Number(dataParams.xInterval ?? 600)
let start = new Date(dataParams.xStart)
let end = new Date(dataParams.xStart)
end.setSeconds(end.getSeconds() + interval)
let { unit, stepSize } = timeParamsByInterval(interval)
if (chart.options.scales?.x) {
chart.options.scales.x.max = end.getTime()
chart.options.scales.x.min = start.getTime()
chart.options.scales.x.ticks.display = dataParams.displayLabels ?? true
chart.options.scales.x.time.unit = unit
chart.options.scales.x.time.stepSize = stepSize
}
}
chart.update()
}, [chart, dataParams, options])
return (<canvas ref={chartRef} />)
}

View File

@ -1,11 +1,30 @@
// import {UserOfWells} from "../components/UserOfWells";
import { AnalysisDepthToDay } from '../components/AnalysisDepthToDay'
import { AnalysisDepthToInterval } from '../components/AnalysisDepthToInterval'
import { AnalysisOperationTime } from '../components/AnalysisOperationTime'
import { Row, Col } from 'antd'
export default function Analysis() {
export default function Analysis(props) {
return (
<div className="menu_title">
<h2>Анализ</h2>
{/*<UserOfWells data={saubData}/>*/}
<hr/>
</div>
<>
<Row>&nbsp;</Row>
<Row justify="space-around" align="middle">
<Col span={10}>
<h2>График Глубина-день</h2>
<AnalysisDepthToDay />
</Col>
<Col span={10}>
<h2>График Глубина за интервал</h2>
<AnalysisDepthToInterval />
</Col>
</Row >
<Row><div style={{height: "150px"}}>&nbsp;</div></Row>
<Row justify="space-around" align="middle">
<Col span={10}>
<h2>График Операция за время</h2>
<AnalysisOperationTime />
</Col>
</Row>
</>
)
}

View File

@ -15,7 +15,7 @@ import moment from 'moment'
import notify from '../components/notify'
import LoaderPortal from '../components/LoaderPortal'
const { RangePicker } = DatePicker;
const { RangePicker } = DatePicker
const SaveObject = (key, obj) => {
let json = JSON.stringify(obj)

View File

@ -47,7 +47,7 @@ const columns = [
];
const filterOptions = [
{label: 'Авария', value: 1},
{label: 'Важное', value: 1},
{label: 'Предупреждение', value: 2},
{label: 'Информация', value: 3},
]
@ -72,6 +72,7 @@ export default function Messages() {
useEffect(() => {
const GetMessages = async () => {
setLoader(true)
try {
let begin = null
let end = null
@ -85,6 +86,12 @@ export default function Messages() {
categories,
begin,
end)
if (paginatedMessages === null){
notify(`Данных по скважине "${id}" нет`, 'warning')
setLoader(false)
return
}
setMessages(paginatedMessages.items.map(m => {
return {
key: m.id,
@ -97,9 +104,10 @@ export default function Messages() {
total: paginatedMessages.count,
current: Math.floor(paginatedMessages.skip / pageSize),
})
} catch (ex) {
notify(`Не удалось загрузить сообщения по скважине "${id}"`, 'error')
console.log(ex)
notify(`Не удалось загрузить сообщения по скважине "${id}"`, 'error')
}
setLoader(false)
}
@ -108,7 +116,6 @@ export default function Messages() {
return (
<>
<div className='filter-group'>
<h3 className='filter-group-heading'>Фильтр сообщений</h3>
<Select

View File

@ -26,11 +26,20 @@ let reportDatesRange = {
to: moment("9999-12-31T23:59:59.9999999")
}
const timePeriodNames = {
600: '1 минута',
86400:'1 день',
604800:'1 неделя'
}
const timePeriodNames = [
{label: '1 секунда', value: 1},
{label: '10 секунд', value: 10},
{label: '1 минута', value: 60},
{label: '5 минут', value: 300},
{label: '30 минут', value: 1800},
{label: '1 час', value: 3600},
{label: '6 часов', value: 21600},
{label: '12 часов', value: 43200},
{label: '1 день', value: 86400},
{label: '1 неделя', value: 604800}
]
const firstStep = timePeriodNames[2]["value"]
const imgPaths = {
'.pdf': '/images/pdf.png',
@ -42,7 +51,7 @@ const imgPaths = {
export default function Report(props) {
const [rangeDate, setRangeDate] = useState([moment().subtract(1,'days'), moment()])
const [step, setStep] = useState(600)
const [step, setStep] = useState(firstStep)
const [format, setFormat] = useState(0)
const [approxPages, setPagesCount] = useState(0)
const [suitableReports, setSuitableReports] = useState([])
@ -51,6 +60,8 @@ export default function Report(props) {
let wellId = useParams().id;
const periods = timePeriodNames.map((line) => <Option key={line.value} value={line.value}>{line.label}</Option>)
const columns = [
{
title: '',
@ -244,15 +255,12 @@ export default function Report(props) {
<Form.Item
label="Шаг графиков"
name="step"
initialValue = {step}
initialValue={step}
className="ml-30px"
>
<Select
onChange={(value) => setStep(value)}
<Select onChange={setStep}
>
<Option value={600}>1 минута</Option>
<Option value={86400}>1 день</Option>
<Option value={604800}>1 неделя</Option>
{periods}
</Select>
</Form.Item>
<Form.Item

View File

@ -191,17 +191,17 @@ const columns = [
},
];
const intervalSteps = [
{label: '10 минут', value: 600},
{label: '30 минут', value: 1800},
{label: '1 час', value: 3600},
{label: '6 часов', value: 21600},
{label: '12 часов', value: 43200},
{label: '1 день', value: 86400},
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 часа'}
]
const defaultInterval = intervalSteps[0]["value"] // поменять тут TODO
export default function TelemetryView(props) {
let {id} = useParams()
const [saubData, setSaubData] = useState([])
@ -212,6 +212,8 @@ export default function TelemetryView(props) {
const periods = intervalSteps.map((line) => <Option key={line.value} value={line.value}>{line.label}</Option>)
const children = timePeriodCollection.map((line) => <Option key={line.value}>{line.label}</Option>)
const handleReceiveDataSaub = (data) => {
if (data) {
setSaubData(data)
@ -268,8 +270,8 @@ export default function TelemetryView(props) {
<span style={{flexGrow: 0.1}}>&nbsp;</span>
<Col>
Интервал:&nbsp;
<Select defaultValue={chartInterval} onChange={setChartInterval}>
{periods}
<Select defaultValue="600" onChange={setChartInterval}>
{children}
</Select>
</Col>
<span style={{flexGrow: 1}}>&nbsp;</span>

View File

@ -41,7 +41,8 @@ html {
.header {
display: flex;
align-items: center;
//padding: 4px 24px;
justify-content: space-around;
gap: 50px;
}
.header .logo {
@ -55,9 +56,7 @@ html {
.header .title{
flex-grow: 1;
color: #fff;
text-align: start;
justify-content: start;
margin-left: 100px;
padding-left: 100px;
}
.header button{
@ -154,9 +153,13 @@ tr.table_row_size {
margin-right: 5px;
}
.header-tree-select {
width: 300px
}
.header-tree-select *{
color: #fff;
font-size: '1.5rem';
font-size: 1rem;
}
.header-tree-select{