From 564ea43a808aad93d9640fa9cd484d0ae02c6b94 Mon Sep 17 00:00:00 2001 From: ts_salikhov Date: Tue, 20 Sep 2022 12:39:05 +0400 Subject: [PATCH 1/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B0?= =?UTF-8?q?=20"=D0=9D=D0=B0=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B0"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/d3/D3HorizontalChart.tsx | 149 ++++++++++++++ src/pages/Telemetry/OperationTime/index.tsx | 210 ++++++++++++++++++++ src/pages/Telemetry/index.jsx | 3 + src/styles/d3.less | 5 + 4 files changed, 367 insertions(+) create mode 100644 src/components/d3/D3HorizontalChart.tsx create mode 100644 src/pages/Telemetry/OperationTime/index.tsx diff --git a/src/components/d3/D3HorizontalChart.tsx b/src/components/d3/D3HorizontalChart.tsx new file mode 100644 index 0000000..421108b --- /dev/null +++ b/src/components/d3/D3HorizontalChart.tsx @@ -0,0 +1,149 @@ +import React, { memo, useEffect } from 'react' +import * as d3 from 'd3' + +import LoaderPortal from '@components/LoaderPortal' +import { useElementSize } from 'usehooks-ts' + + +import '@styles/d3.less' + +type DataType = { + name: string + percent: number +} + +type D3HorizontalChartProps = { + width?: string + height?: string + data: DataType[] + colors?: string[] +} + +const D3HorizontalChart = memo(( + { + width: givenWidth = '100%', + height: givenHeight = '100%', + data, + colors + }: D3HorizontalChartProps) => { + + const [rootRef, { width, height }] = useElementSize() + + const margin = { top: 50, right: 100, bottom: 50, left: 100 } + + useEffect(() => { + if (width < 100 || height < 100) return + const _width = width - margin.left - margin.right + const _height = height - margin.top - margin.bottom + + const svg = d3.select('#d3-horizontal-chart') + .attr('width', '100%') + .attr('height', '100%') + .append('g') + .attr('transform', `translate(${margin.left},${margin.top})`) + + const percents = ['percents'] + const names = data.map(d => d.name) + + const stackedData = d3.stack() + //@ts-ignore + .keys(percents)(data) + + const xMax = 100 + + // scales + + const x = d3.scaleLinear() + .domain([0, xMax]) + .range([0, _width]) + + const y = d3.scaleBand() + .domain(names) + .range([0, _height]) + .padding(0.25) + + // axes + + const xAxisTop = d3.axisTop(x) + .tickValues([0, 25, 50, 75, 100]) + .tickFormat(d => d + '%') + + const xAxisBottom = d3.axisBottom(x) + .tickValues([0, 25, 50, 75, 100]) + .tickFormat(d => d + '%') + + const yAxisLeft = d3.axisLeft(y) + + const gridlines = d3.axisBottom(x) + .tickValues([0, 25, 50, 75, 100]) + .tickFormat(d => '') + .tickSize(_height) + + const yAxisRight = d3.axisRight(y) + .ticks(0) + .tickValues([]) + .tickFormat(d => '') + + svg.append('g') + .attr('transform', `translate(0,0)`) + .attr("class", "grid-line") + .call(g => g.select('.domain').remove()) + .call(gridlines) + + svg.append('g') + .attr('transform', `translate(0,0)`) + .call(xAxisTop) + + svg.append("g") + .call(yAxisLeft) + + svg.append('g') + .attr('transform', `translate(0,${_height})`) + .call(xAxisBottom) + + svg.append('g') + .attr('transform', `translate(${_width},0)`) + .call(yAxisRight) + + const layers = svg.append('g') + .selectAll('g') + .data(stackedData) + .join('g') + + // transition for bars + const duration = 1000 + const t = d3.transition() + .duration(duration) + .ease(d3.easeLinear) + + layers.each(function() { + d3.select(this) + .selectAll('rect') + //@ts-ignore + .data(d => d) + .join('rect') + .attr('fill', (d, i) => colors ? colors[i] : 'black') + //@ts-ignore + .attr('y', d => y(d.data.name)) + .attr('height', y.bandwidth()) + //@ts-ignore + .transition(t) + //@ts-ignore + .attr('width', d => x(d.data.percent)) + }) + + return () => { + svg.selectAll("g").selectAll("*").remove() + } + }, [width, height, data]) + + return ( + +
+ +
+
+ ) +}) + +export default D3HorizontalChart \ No newline at end of file diff --git a/src/pages/Telemetry/OperationTime/index.tsx b/src/pages/Telemetry/OperationTime/index.tsx new file mode 100644 index 0000000..8764d34 --- /dev/null +++ b/src/pages/Telemetry/OperationTime/index.tsx @@ -0,0 +1,210 @@ +import React, { ReactNode, useEffect, useState } from 'react' +import { Col, Row, Select } from 'antd' +import { Moment } from 'moment' + +import { DateRangeWrapper, makeColumn, Table } from '@components/Table' +import LoaderPortal from '@components/LoaderPortal' +import { arrayOrDefault, wrapPrivateComponent } from '@utils' +import D3HorizontalChart from '@components/d3/D3HorizontalChart' +import { useWell } from '@asb/context' +import { invokeWebApiWrapperAsync } from '@components/factory' +import { SubsystemOperationTimeService } from '@api' + +const { Option } = Select; + +type DataType = { + idSubsystem: number + subsystemName: string + usedTimeHours: number + kUsage: number + k2: number +} + +const _data: DataType[] = [ + { + "idSubsystem": 1, + "subsystemName": "Keруррурk", + "usedTimeHours": 2, + "kUsage": 0.45, + "k2": 1 + }, + { + "idSubsystem": 2, + "subsystemName": "Lol", + "usedTimeHours": 0, + "kUsage": 0.24, + "k2": 0 + }, + { + "idSubsystem": 3, + "subsystemName": "kek", + "usedTimeHours": 0, + "kUsage": 0.37, + "k2": 0 + }, + { + "idSubsystem": 4, + "subsystemName": "keklol", + "usedTimeHours": 0, + "kUsage": 0.73, + "k2": 0 + }, + { + "idSubsystem": 5, + "subsystemName": "Keруррурk1", + "usedTimeHours": 2, + "kUsage": 0.45, + "k2": 1 + }, + { + "idSubsystem": 6, + "subsystemName": "Lol2", + "usedTimeHours": 0, + "kUsage": 0.24, + "k2": 0 + }, + { + "idSubsystem": 7, + "subsystemName": "kek3", + "usedTimeHours": 0, + "kUsage": 0.37, + "k2": 0 + }, + { + "idSubsystem": 8, + "subsystemName": "keklol4", + "usedTimeHours": 0, + "kUsage": 0.73, + "k2": 0 + }, +] + +const subsystemColors = [ + '#1abc9c', + '#16a085', + '#2ecc71', + '#27ae60', + '#3498db', + '#2980b9', + '#9b59b6', + '#8e44ad', + '#34495e', + '#2c3e50', + '#f1c40f', + '#f39c12', + '#e67e22', + '#d35400', + '#e74c3c', + '#c0392b', + '#ecf0f1', + '#bdc3c7', + '#95a5a6', + '#7f8c8d', +] + +const tableColumns = [ + makeColumn('Цвет', 'color', { width: 50, render: (color) => ( +
+ ) }), + makeColumn('Подсистема', 'subsystemName'), + makeColumn('Время работы, ч', 'usedTimeHours'), + makeColumn('Активность, %', 'kUsage', { render: (d) => d * 100 }) +] + +const OperationTime = () => { + const [showLoader, setShowLoader] = useState(false) + const [data1, setData1] = useState([]) + const [data, setData] = useState(_data) + const [dateRange, setDateRange] = useState([]) + const [childrenData, setChildrenData] = useState([]) + const [well] = useWell() + + const errorNotifyText = `Не удалось загрузить данные` + + useEffect(() => { + + invokeWebApiWrapperAsync( + async () => { + if (!well.id) return + try { + setData1(arrayOrDefault(await SubsystemOperationTimeService.getStat( + well.id, + undefined, + dateRange[1] ? dateRange[0]?.toISOString() : undefined, + dateRange[1]?.toISOString(), + ))) + } catch(e) { + setData1([]) + throw e + } + }, + setShowLoader, + errorNotifyText, + { actionName: 'Получение данных по скважине', well } + ) + }, [dateRange]) + + useEffect(() => { + setChildrenData(_data.map((item) => ( + + ))) + }, [data]) + + const selectChange = (value: string[]) => { + + setData(_data.reduce((previousValue: DataType[], currentValue) => { + if (value.includes(currentValue.subsystemName)) { + previousValue.push(currentValue) + } + return previousValue + }, [])) + + } + + return ( + +

Фильтр подсистем

+ + + + + + setDateRange(dateRange)} + value={[dateRange[0], dateRange[1]]} + /> + + +
+ ({name: item.subsystemName, percent: item.kUsage * 100}))} + colors={subsystemColors} + width={'100%'} + height={'50vh'} + /> +
+ ({...d, color: subsystemColors[i]}))} + scroll={{ y: '25vh', x: true }} + pagination={false} + /> + + ) +} + +export default wrapPrivateComponent(OperationTime, { + requirements: [], + title: 'Наработка', + route: 'operation_time', +}) \ No newline at end of file diff --git a/src/pages/Telemetry/index.jsx b/src/pages/Telemetry/index.jsx index 9048ba5..d85f7e8 100755 --- a/src/pages/Telemetry/index.jsx +++ b/src/pages/Telemetry/index.jsx @@ -12,6 +12,7 @@ import Messages from './Messages' import Operations from './Operations' import DashboardNNB from './DashboardNNB' import TelemetryView from './TelemetryView' +import OperationTime from './OperationTime' import '@styles/index.css' @@ -30,6 +31,7 @@ const Telemetry = memo(() => { } /> + @@ -43,6 +45,7 @@ const Telemetry = memo(() => { } /> } /> } /> + } /> diff --git a/src/styles/d3.less b/src/styles/d3.less index c009c17..2e48ca4 100644 --- a/src/styles/d3.less +++ b/src/styles/d3.less @@ -116,6 +116,11 @@ } } +.grid-line line { + stroke: #ddd; + stroke-dasharray: 4 +} + @media (max-width: 1800px) { .asb-d3-chart .adaptive-tooltip { font-size: 11px; From 1f8bf12821b2a096ab4f2a728ee928140ce373cb Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Wed, 28 Sep 2022 18:04:55 +0500 Subject: [PATCH 2/2] OperationTime. Adapt Dto. --- src/pages/Telemetry/OperationTime/index.tsx | 81 +++------------------ 1 file changed, 12 insertions(+), 69 deletions(-) diff --git a/src/pages/Telemetry/OperationTime/index.tsx b/src/pages/Telemetry/OperationTime/index.tsx index 8764d34..d442d29 100644 --- a/src/pages/Telemetry/OperationTime/index.tsx +++ b/src/pages/Telemetry/OperationTime/index.tsx @@ -2,7 +2,7 @@ import React, { ReactNode, useEffect, useState } from 'react' import { Col, Row, Select } from 'antd' import { Moment } from 'moment' -import { DateRangeWrapper, makeColumn, Table } from '@components/Table' +import { DateRangeWrapper, makeColumn, makeNumericRender, Table } from '@components/Table' import LoaderPortal from '@components/LoaderPortal' import { arrayOrDefault, wrapPrivateComponent } from '@utils' import D3HorizontalChart from '@components/d3/D3HorizontalChart' @@ -17,68 +17,10 @@ type DataType = { subsystemName: string usedTimeHours: number kUsage: number - k2: number + sumDepthInterval: number + operationCount: number } -const _data: DataType[] = [ - { - "idSubsystem": 1, - "subsystemName": "Keруррурk", - "usedTimeHours": 2, - "kUsage": 0.45, - "k2": 1 - }, - { - "idSubsystem": 2, - "subsystemName": "Lol", - "usedTimeHours": 0, - "kUsage": 0.24, - "k2": 0 - }, - { - "idSubsystem": 3, - "subsystemName": "kek", - "usedTimeHours": 0, - "kUsage": 0.37, - "k2": 0 - }, - { - "idSubsystem": 4, - "subsystemName": "keklol", - "usedTimeHours": 0, - "kUsage": 0.73, - "k2": 0 - }, - { - "idSubsystem": 5, - "subsystemName": "Keруррурk1", - "usedTimeHours": 2, - "kUsage": 0.45, - "k2": 1 - }, - { - "idSubsystem": 6, - "subsystemName": "Lol2", - "usedTimeHours": 0, - "kUsage": 0.24, - "k2": 0 - }, - { - "idSubsystem": 7, - "subsystemName": "kek3", - "usedTimeHours": 0, - "kUsage": 0.37, - "k2": 0 - }, - { - "idSubsystem": 8, - "subsystemName": "keklol4", - "usedTimeHours": 0, - "kUsage": 0.73, - "k2": 0 - }, -] - const subsystemColors = [ '#1abc9c', '#16a085', @@ -107,14 +49,15 @@ const tableColumns = [
) }), makeColumn('Подсистема', 'subsystemName'), - makeColumn('Время работы, ч', 'usedTimeHours'), - makeColumn('Активность, %', 'kUsage', { render: (d) => d * 100 }) + makeColumn('% использования', 'kUsage', { render: val => (+val * 100).toFixed(2) }), + makeColumn('Проходка, м', 'sumDepthInterval', {render: makeNumericRender(2)}), + makeColumn('Время работы, ч', 'usedTimeHours', {render: makeNumericRender(2)}), + makeColumn('Кол-во запусков', 'operationCount'), ] const OperationTime = () => { const [showLoader, setShowLoader] = useState(false) - const [data1, setData1] = useState([]) - const [data, setData] = useState(_data) + const [data, setData] = useState([]) const [dateRange, setDateRange] = useState([]) const [childrenData, setChildrenData] = useState([]) const [well] = useWell() @@ -127,14 +70,14 @@ const OperationTime = () => { async () => { if (!well.id) return try { - setData1(arrayOrDefault(await SubsystemOperationTimeService.getStat( + setData(arrayOrDefault(await SubsystemOperationTimeService.getStat( well.id, undefined, dateRange[1] ? dateRange[0]?.toISOString() : undefined, dateRange[1]?.toISOString(), ))) } catch(e) { - setData1([]) + setData([]) throw e } }, @@ -145,7 +88,7 @@ const OperationTime = () => { }, [dateRange]) useEffect(() => { - setChildrenData(_data.map((item) => ( + setChildrenData(data.map((item) => ( @@ -154,7 +97,7 @@ const OperationTime = () => { const selectChange = (value: string[]) => { - setData(_data.reduce((previousValue: DataType[], currentValue) => { + setData(data.reduce((previousValue: DataType[], currentValue) => { if (value.includes(currentValue.subsystemName)) { previousValue.push(currentValue) }