Графики для аналитики

This commit is contained in:
Alexey 2021-07-19 17:28:09 +05:00
parent 46234d6be1
commit f62b280ee5
16 changed files with 753 additions and 57 deletions

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,38 @@
import { useParams } from "react-router-dom"
import notify from "../components/notify"
import { useState, useEffect } from 'react'
import { AnalyticsService } from "../services/api"
import { ChartDepthToInterval } from './charts/ChartDepthToInterval'
const line = {label: 'Данные по глубине скважины за период', y: 'intervalDepthProgress', x: 'intervalStartDate'}
export function AnalysisDepthToInterval() {
let { id } = useParams()
const [depthToIntervalData, setDepthToIntervalData] = useState([])
const [loader, setLoader] = useState(false)
const handleReceiveDepthToIntervalData = (data) => {
setDepthToIntervalData(data)
}
useEffect(() => {
setLoader(true)
AnalyticsService.getWellDepthToInterval(id)
.then(handleReceiveDepthToIntervalData)
.catch(error => {
notify(`Не удалось получить данные для Анализа Глубина-День по скважине "${id}"`,
'warning')
console.log(error)
})
.finally(setLoader(false))
}, [id])
return (
<>
<ChartDepthToInterval
data={depthToIntervalData}
line={line}
/>
</>
)
}

View File

@ -1,54 +1,38 @@
import { useEffect, useState} from 'react'
import {GetRandomColor} from "./ChartTimeArchive"
import {ChartDepthToDayBase} from "./ChartDepthToDayBase";
import {ChartTimeBase} from "./ChartTimeBase";
import { useEffect, useState } from 'react'
import { ChartDepthToDayBase } from './ChartDepthToDayBase'
import { CreateDataset } from './ChartTimeArchive'
const CreateDataset = (lineConfig) => {
let color = lineConfig.borderColor
?? lineConfig.backgroundColor
?? lineConfig.color
?? GetRandomColor()
let dataset = {
label: lineConfig.label,
data: [],
backgroundColor: lineConfig.backgroundColor ?? color,
borderColor: lineConfig.borderColor ?? color,
borderWidth: lineConfig.borderWidth ?? 1,
borderDash: lineConfig.dash ?? [],
}
return dataset
}
export const ChartAnalyticDepthToDay = ({lines, data, yDisplay, rangeDate, chartRatio}) => {
const [analyticDataParams, setAnalyticDataParams] = useState({data:{datasets:[]}})
let startOfDay = new Date().setHours(0,0,0,0)
export const ChartDepthToDay = ({data, lines}) => {
const [depthToDayDataParams, setDepthToDayDataParams] = useState({data: {datasets: []}})
useEffect(() => {
if ((!lines)
|| (!data))
return
let newDatasets = lines.map(lineCfg => {
let dataset = CreateDataset(lineCfg)
dataset.data = data.map(dataItem => {
return {
x: new Date(dataItem[lineCfg.xAccessorName??'date']),
y: dataItem[lineCfg.yAccessorName],
}
})
return dataset
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 = {
xStart: startOfDay,
displayLabels: yDisplay??false,
displayLabels: true,
data: {
datasets: newDatasets
}
}
setAnalyticDataParams(newParams)
}, [data, lines, yDisplay, rangeDate, chartRatio])
setDepthToDayDataParams(newParams)
}, [data, lines])
return (<ChartTimeBase dataParams={analyticDataParams}/>)
return (
<ChartDepthToDayBase dataParams={depthToDayDataParams} />
)
}

View File

@ -0,0 +1,236 @@
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: 1.45,
animation: false,
tooltips: {
enabled: true,
callbacks: {
label(tooltipItem:any) {
return tooltipItem.yLabel;
}
}
},
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
scales: {
x:{
type: 'time',
reverse: true,
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 : false,
textStrokeColor : "#ffff",
textStrokeWidth : 2,
color:"#000",
}
},
y:{
type:'linear',
position:'top'
}
},
elements:{
point:{
radius:0,
hoverRadius:5,
},
},
plugins:{
legend:{
display: true,
},
datalabels: {
display: false,
},
// zoom: {
// zoom: {
// wheel: {
// enabled: true,
// modifierKey: 'alt',
// },
// pinch: {
// enabled: true
// },
// mode: 'x',
// }
// },
}
}
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,37 @@
import { useEffect, useState } from 'react'
import { ChartDepthToIntervalBase } from './ChartDepthToIntervalBase'
import { CreateDataset } from './ChartTimeArchive'
export function ChartDepthToInterval({data, lines}) {
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.x??'date']),
y: dataItem[lineCfg.y],
}
})
return datasets
})
let newParams = {
displayLabels: true,
data: {
datasets: newDatasets
}
}
setDepthToIntervalDataParams(newParams)
}, [data, lines])
return (
<ChartDepthToIntervalBase dataParams={depthToIntervalDataParams} />
)
}

View File

@ -0,0 +1,221 @@
import {useEffect, useRef, useState} from 'react'
import {
Chart,
TimeScale,
Legend,
PointElement,
ChartData,
ChartTypeRegistry,
ChartOptions,
BarController,
BarElement
} from 'chart.js'
import 'chartjs-adapter-moment'
import ChartDataLabels from 'chartjs-plugin-datalabels'
Chart.register(TimeScale, BarController, BarElement, PointElement, Legend, ChartDataLabels)
const defaultOptions = {
responsive: true,
aspectRatio: 1.45,
animation: false,
tooltips: {
enabled: true,
callbacks: {
label(tooltipItem:any) {
return tooltipItem.yLabel;
}
}
},
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
scales: {
x:{
type: 'time',
reverse: true,
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 : false,
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 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

@ -19,7 +19,15 @@ Chart.register( TimeScale, LinearScale, LineController, LineElement, PointElemen
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:{
@ -80,7 +88,7 @@ const defaultOptions = {
},
mode: 'x',
}
}
},
}
}
@ -110,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'
@ -138,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)
@ -111,6 +110,9 @@ export const ChartTimeOnline: React.FC<ChartTimeProps> = (props) => {
anchor: 'center',
clip: true
},
tooltip: {
enable: true
}
}
}

View File

@ -1,11 +1,12 @@
// import {UserOfWells} from "../components/UserOfWells";
import { AnalysisDepthToDay } from '../components/AnalysisDepthToDay'
import { AnalysisDepthToInterval } from '../components/AnalysisDepthToInterval'
export default function Analysis(props) {
export default function Analysis() {
return (
<div className="menu_title">
<h2>Анализ</h2>
{/*<UserOfWells data={saubData}/>*/}
<hr/>
</div>
<>
<AnalysisDepthToDay />
<AnalysisDepthToInterval />
</>
)
}

View File

@ -10,12 +10,16 @@ export type { DatesRangeDto } from './models/DatesRangeDto';
export type { EventDto } from './models/EventDto';
export type { MessageDto } from './models/MessageDto';
export type { MessageDtoPaginationContainer } from './models/MessageDtoPaginationContainer';
export type { OperationDurationDto } from './models/OperationDurationDto';
export type { TelemetryInfoDto } from './models/TelemetryInfoDto';
export type { TelemetryMessageDto } from './models/TelemetryMessageDto';
export type { TelemetryUserDto } from './models/TelemetryUserDto';
export type { UserTokenDto } from './models/UserTokenDto';
export type { WellDepthToDayDto } from './models/WellDepthToDayDto';
export type { WellDepthToIntervalDto } from './models/WellDepthToIntervalDto';
export type { WellDto } from './models/WellDto';
export { AnalyticsService } from './services/AnalyticsService';
export { AuthService } from './services/AuthService';
export { DataService } from './services/DataService';
export { MessageService } from './services/MessageService';

View File

@ -0,0 +1,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type OperationDurationDto = {
processName?: string | null;
duration?: number;
}

View File

@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type WellDepthToDayDto = {
wellDepth?: number;
bitDepth?: number;
date?: string;
}

View File

@ -0,0 +1,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type WellDepthToIntervalDto = {
intervalStartDate?: string;
intervalDepthProgress?: number;
}

View File

@ -0,0 +1,99 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { OperationDurationDto } from '../models/OperationDurationDto';
import type { WellDepthToDayDto } from '../models/WellDepthToDayDto';
import type { WellDepthToIntervalDto } from '../models/WellDepthToIntervalDto';
import { request as __request } from '../core/request';
export class AnalyticsService {
/**
* Возвращает данные по скважине "глубина-день"
* @param wellId id скважины
* @returns WellDepthToDayDto Success
* @throws ApiError
*/
public static async getWellDepthToDay(
wellId: number,
): Promise<Array<WellDepthToDayDto>> {
const result = await __request({
method: 'GET',
path: `/api/analytics/${wellId}/wellDepthToDay`,
});
return result.body;
}
/**
* Возвращает данные по глубине скважины за период
* @param wellId id скважины
* @param intervalHoursTimestamp количество секунд в необходимом интервале времени
* @param workBeginTimestamp количество секунд в времени начала смены
* @returns WellDepthToIntervalDto Success
* @throws ApiError
*/
public static async getWellDepthToInterval(
wellId: number,
intervalHoursTimestamp?: number,
workBeginTimestamp?: number,
): Promise<Array<WellDepthToIntervalDto>> {
const result = await __request({
method: 'GET',
path: `/api/analytics/${wellId}/wellDepthToInterval`,
query: {
'intervalHoursTimestamp': intervalHoursTimestamp,
'workBeginTimestamp': workBeginTimestamp,
},
});
return result.body;
}
/**
* Возвращает данные по операциям на скважине "операции-время"
* @param wellId id скважины
* @param begin дата начала интервала
* @param end дата окончания интервала
* @returns OperationDurationDto Success
* @throws ApiError
*/
public static async getOperationsSummary(
wellId: number,
begin?: string,
end?: string,
): Promise<Array<OperationDurationDto>> {
const result = await __request({
method: 'GET',
path: `/api/analytics/${wellId}/operationsSummary`,
query: {
'begin': begin,
'end': end,
},
});
return result.body;
}
/**
* Возвращает детальные данные по операциям на скважине за период
* @param wellId id скважины
* @param intervalHoursTimestamp количество секунд в необходимом интервале времени
* @param workBeginTimestamp количество секунд в времени начала смены
* @returns OperationDurationDto Success
* @throws ApiError
*/
public static async getOperationsToInterval(
wellId: number,
intervalHoursTimestamp?: number,
workBeginTimestamp?: number,
): Promise<Array<OperationDurationDto>> {
const result = await __request({
method: 'GET',
path: `/api/analytics/${wellId}/operationsToInterval`,
query: {
'intervalHoursTimestamp': intervalHoursTimestamp,
'workBeginTimestamp': workBeginTimestamp,
},
});
return result.body;
}
}