diff --git a/src/pages/Telemetry/Operations/OperationsChart.jsx b/src/pages/Telemetry/Operations/OperationsChart.jsx
index 8345f18..396aac6 100644
--- a/src/pages/Telemetry/Operations/OperationsChart.jsx
+++ b/src/pages/Telemetry/Operations/OperationsChart.jsx
@@ -1,24 +1,36 @@
import { memo, useEffect, useMemo, useRef, useState } from 'react'
import * as d3 from 'd3'
+import { makePointsOptimizator } from '@utils/functions/chart'
+
import '@styles/detected_operations.less'
-export const OperationsChart = memo(({ data, yDomain, width, height, bottom = 30, left = 50, top = 10, right = 10, color = '#00F' }) => {
+const optimizePoints = makePointsOptimizator((a, b) => a.target === b.target)
+
+export const OperationsChart = memo(({ data, yDomain, width, height, bottom = 30, left = 50, top = 10, right = 10, color = '#00F', targetColor = '#F00' }) => {
const [ref, setRef] = useState(null)
+
const axisX = useRef(null)
const axisY = useRef(null)
+ const targetPath = useRef(null)
+ const chartBars = useRef(null)
+
const w = useMemo(() => Number.isFinite(+width) ? +width : ref?.offsetWidth, [width, ref])
const h = useMemo(() => Number.isFinite(+height) ? +height : ref?.offsetHeight, [height, ref])
const d = useMemo(() => data.map((row) => ({
date: new Date(row.dateStart),
value: row.durationMinutes,
+ target: row.operationValue?.targetValue,
})), [data]) // Нормализуем данные для графика
const x = useMemo(() => d3
.scaleTime()
.range([0, w - left - right])
- .domain([d3.min(d, d => d.date), d3.max(d, d => d.date)])
+ .domain([
+ d3.min(d, d => d.date),
+ d3.max(d, d => d.date)
+ ])
, [w, d, left, right]) // Создаём ось X
const y = useMemo(() => d3
@@ -27,25 +39,42 @@ export const OperationsChart = memo(({ data, yDomain, width, height, bottom = 30
.domain([0, yDomain ?? d3.max(d, d => d.value)])
, [h, d, top, bottom, yDomain]) // Создаём ось Y
- const lines = useMemo(() => d.map(d => ({ x: x(d.date), y: y(d.value) })), [d, x, y]) // Получаем массив координат линий
+ const targetLine = useMemo(() => d3.line()
+ .x(d => x(d.date))
+ .y(d => y(d.target))
+ .defined(d => (d.target ?? null) !== null && !Number.isNaN(d.target))
+ , [x, y])
- useEffect(() => {
- d3.select(axisX.current).call(d3.axisBottom(x))
- }, [axisX, x]) // Рисуем ось X
+ useEffect(() => { d3.select(targetPath.current).attr('d', targetLine(optimizePoints(d))) }, [d, targetLine, targetColor]) // Рисуем линию целевого значения
+ useEffect(() => { d3.select(axisX.current).call(d3.axisBottom(x)) }, [axisX, x]) // Рисуем ось X
+ useEffect(() => { d3.select(axisY.current).call(d3.axisLeft(y)) }, [axisY, y]) // Рисуем ось Y
- useEffect(() => {
- d3.select(axisY.current).call(d3.axisLeft(y))
- }, [axisY, y]) // Рисуем ось Y
+ useEffect(() => { // Рисуем столбики операций
+ const bars = d3
+ .select(chartBars.current)
+ .selectAll('line')
+ .data(d)
+
+ bars.exit().remove() // Удаляем лишние линии
+
+ bars.enter()
+ .append('line') // Создаём новые линии, если не хватает
+ .merge(bars) // Объединяем с существующими
+ .attr('x1', d => x(d.date)) // Присваиваем значения
+ .attr('x2', d => x(d.date))
+ .attr('y1', h - bottom - top)
+ .attr('y2', d => y(d.value))
+
+ }, [d, x, y, color, h, bottom, top])
return (
diff --git a/src/pages/Telemetry/Operations/TargetEditor.jsx b/src/pages/Telemetry/Operations/TargetEditor.jsx
index 415175d..3583edd 100644
--- a/src/pages/Telemetry/Operations/TargetEditor.jsx
+++ b/src/pages/Telemetry/Operations/TargetEditor.jsx
@@ -15,7 +15,7 @@ const columnOptions = {
const scroll = { y: '75vh', scrollToFirstRowOnChange: true }
const numericRender = makeNumericRender(2)
-export const TargetEditor = memo(({ loading }) => {
+export const TargetEditor = memo(({ loading, onChange }) => {
const [targets, setTargets] = useState([])
const [showModal, setShowModal] = useState(false)
const [showLoader, setShowLoader] = useState(false)
@@ -33,23 +33,23 @@ export const TargetEditor = memo(({ loading }) => {
'Получение списка целей',
), [idWell])
- const onModalOpen = useCallback(() => {
- setShowModal(true)
- }, [])
-
- const onModalCancel = useCallback(() => {
- setShowModal(false)
- }, [])
+ const onModalOpen = useCallback(() => setShowModal(true), [])
+ const onModalCancel = useCallback(() => setShowModal(false), [])
const recordParser = useCallback((record) => ({ ...record, idWell }), [idWell])
+ const onTargetChange = useCallback(() => {
+ updateTable()
+ onChange?.()
+ }, [updateTable, onChange])
+
const isLoading = useMemo(() => loading || showLoader, [loading, showLoader])
const tableHandlers = useMemo(() => {
const handlerProps = {
service: OperationValueService,
setLoader: setShowLoader,
- onComplete: updateTable,
+ onComplete: onTargetChange,
permission: 'OperationValue.edit',
}
@@ -58,7 +58,7 @@ export const TargetEditor = memo(({ loading }) => {
edit: { ...handlerProps, action: 'update', actionName: 'Редактирование цели', recordParser },
delete: { ...handlerProps, action: 'delete', actionName: 'Удаление цели', permission: 'OperationValue.delete' },
}
- }, [updateTable, recordParser])
+ }, [onTargetChange, recordParser])
useEffect(() => {
invokeWebApiWrapperAsync(
diff --git a/src/pages/Telemetry/Operations/index.jsx b/src/pages/Telemetry/Operations/index.jsx
index 0287ff2..e1ed381 100644
--- a/src/pages/Telemetry/Operations/index.jsx
+++ b/src/pages/Telemetry/Operations/index.jsx
@@ -48,6 +48,17 @@ const Operations = memo(() => {
'Получение списка бурильщиков'
), [])
+ const updateData = useCallback(async () => invokeWebApiWrapperAsync(
+ async () => {
+ if (!dates) return
+ const data = await DetectedOperationService.get(idWell, undefined, dates[0].toISOString(), dates[1].toISOString())
+ setData(data)
+ },
+ setIsLoading,
+ 'Не удалось загрузить список определённых операций',
+ 'Получение списка определённых операций',
+ ), [idWell, dates])
+
useEffect(() => {
if (permissions.driller.get)
updateDrillers()
@@ -70,17 +81,8 @@ const Operations = memo(() => {
}, [idWell])
useEffect(() => {
- invokeWebApiWrapperAsync(
- async () => {
- if (!dates) return
- const data = await DetectedOperationService.get(idWell, undefined, dates[0].toISOString(), dates[1].toISOString())
- setData(data)
- },
- setIsLoading,
- 'Не удалось загрузить список определённых операций',
- 'Получение списка определённых операций',
- )
- }, [idWell, dates])
+ updateData()
+ }, [updateData])
return (
@@ -110,7 +112,7 @@ const Operations = memo(() => {
>
)}
{permissions.detectedOperation.get && permissions.operationValue.get && (
-
+
)}
diff --git a/src/utils/functions/chart.ts b/src/utils/functions/chart.ts
new file mode 100644
index 0000000..4d76019
--- /dev/null
+++ b/src/utils/functions/chart.ts
@@ -0,0 +1,10 @@
+export const makePointsOptimizator = (isEquals: (a: Record, b: Record) => boolean) => (points: Record[]) => {
+ if (!Array.isArray(points) || points.length < 3) return points
+
+ const out: Record[] = []
+ for (let i = 1; i < points.length - 1; i++)
+ if (!isEquals(points[i - 1], points[i]) || !isEquals(points[i], points[i + 1]))
+ out.push(points[i])
+
+ return [points[0], ...out, points[points.length - 1]]
+}