From 934eab2f3c5ff7afe02a54319298cdf308be3674 Mon Sep 17 00:00:00 2001 From: goodmice Date: Thu, 11 Aug 2022 17:14:36 +0500 Subject: [PATCH] =?UTF-8?q?*=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=80=D0=B8=D0=B2?= =?UTF-8?q?=D1=8F=D0=B7=D0=BA=D0=B8=20*=20=D0=98=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE=20=D1=81=D0=BE=D1=85=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/d3/D3MonitoringChartEditor.tsx | 33 +++++- src/components/d3/D3MonitoringCharts.tsx | 105 +++++++++++------- .../d3/D3MonitoringGroupsEditor.tsx | 60 ++++++---- src/components/d3/types.ts | 2 +- src/pages/Telemetry/Archive/index.jsx | 3 +- src/pages/Telemetry/TelemetryView/index.jsx | 9 +- 6 files changed, 137 insertions(+), 75 deletions(-) diff --git a/src/components/d3/D3MonitoringChartEditor.tsx b/src/components/d3/D3MonitoringChartEditor.tsx index 02bad6e..462e9ff 100644 --- a/src/components/d3/D3MonitoringChartEditor.tsx +++ b/src/components/d3/D3MonitoringChartEditor.tsx @@ -1,5 +1,5 @@ import { Button, Form, FormItemProps, Input, InputNumber, Select, Tooltip } from 'antd' -import { CSSProperties, memo, useCallback, useEffect, useMemo, useState } from 'react' +import { memo, useCallback, useEffect, useMemo } from 'react' import { ColorPicker, Color } from '../ColorPicker' import { ExtendedChartDataset } from './D3MonitoringCharts' @@ -18,11 +18,13 @@ const lineTypes = [ ] export type D3MonitoringChartEditorProps = { + group: ExtendedChartDataset[] chart: ExtendedChartDataset onChange: (value: ExtendedChartDataset) => boolean } const _D3MonitoringChartEditor = ({ + group, chart: value, onChange, }: D3MonitoringChartEditorProps) => { @@ -30,6 +32,7 @@ const _D3MonitoringChartEditor = ({ const onSave = useCallback((props: Partial>) => { const values = form.getFieldsValue() + if (!values['label']) return const newValue = { ...value, ...values, @@ -51,16 +54,34 @@ const _D3MonitoringChartEditor = ({ useEffect(() => { if (value.type) - form.setFieldsValue(value) + form.setFieldsValue({ + linkedTo: null, + label: null, + shortLabel: null, + ...value, + }) else form.resetFields() }, [value, form]) + const options = useMemo(() => group.filter((chart) => chart.key !== value.key).map((chart) => ({ + value: chart.key, + label: chart.label, + })), [group, value]) + return (
- + + onSave({ label: e.target.value })} /> @@ -69,10 +90,10 @@ const _D3MonitoringChartEditor = ({ - onDomainChange({ min })} placeholder={'Мин'} /> - onDomainChange({ max })} placeholder={'Макс'} /> + onDomainChange({ min })} placeholder={'Мин'} /> + onDomainChange({ max })} placeholder={'Макс'} /> diff --git a/src/components/d3/D3MonitoringCharts.tsx b/src/components/d3/D3MonitoringCharts.tsx index 379078a..86ee9c0 100644 --- a/src/components/d3/D3MonitoringCharts.tsx +++ b/src/components/d3/D3MonitoringCharts.tsx @@ -5,7 +5,9 @@ import { Empty } from 'antd' import * as d3 from 'd3' import LoaderPortal from '@components/LoaderPortal' +import { invokeWebApiWrapperAsync } from '@components/factory' import { isDev, usePartialProps } from '@utils' +import { UserSettingsService } from '@api' import { ChartAxis, @@ -23,22 +25,25 @@ import { D3HorizontalCursorSettings } from './plugins' import D3MouseZone from './D3MouseZone' +import D3MonitoringGroupsEditor from './D3MonitoringGroupsEditor' import { getByAccessor, getChartClass, getGroupClass, getTicks } from './functions' import { renderArea, renderLine, renderNeedle, renderPoint, renderRectArea } from './renders' -import D3MonitoringGroupsEditor from './D3MonitoringGroupsEditor' -import { UserSettingsService } from '@asb/services/api' -import { invokeWebApiWrapperAsync } from '../factory' const roundTo = (v: number, to: number = 50) => { - if (to == 0) return v - if (v < 0) return Math.round(v / to) * to - return Math.ceil(v / to) * to + if (v === 0) return v + return (v > 0 ? Math.ceil : Math.round)(v / to) * to } -const calculateDomain = (mm: MinMax, round: number = 100): Required => { +const calculateDomain = (mm: MinMax): Required => { + let round = Math.abs((mm.max ?? 0) - (mm.min ?? 0)) + if (round < 10) round = 10 + else if (round < 100) round = roundTo(round, 10) + else if (round < 1000) round = roundTo(round, 100) + else if (round < 10000) round = roundTo(round, 1000) + else round = 0 let min = roundTo(mm.min ?? 0, round) let max = roundTo(mm.max ?? round, round) - if (min - max < round) { + if (round && Math.abs(min - max) < round) { const mid = (min + max) / 2 min = mid - round max = mid + round @@ -86,7 +91,7 @@ const getDefaultYTicks = (): Required> => ({ /** * @template DataType тип данных отображаемых записей */ -export type D3MonitoringChartsProps> = React.DetailedHTMLProps, HTMLDivElement> & { +export type D3MonitoringChartsProps> = Omit, HTMLDivElement>, 'ref'> & { /** Двумерный массив датасетов (группа-график) */ datasetGroups: ExtendedChartDataset[][] /** Ширина графика числом пикселей или CSS-значением (px/%/em/rem) */ @@ -126,6 +131,9 @@ export type D3MonitoringChartsProps> = Reac spaceBetweenGroups?: number /** Название графика для сохранения в базе */ chartName?: string + methods?: (value: { + setSettingsVisible: (visible: boolean) => void + }) => void } export type ChartSizes = ChartOffset & { @@ -171,6 +179,7 @@ const _D3MonitoringCharts = >({ spaceBetweenGroups = 30, dash, chartName, + methods, className = '', ...other @@ -181,32 +190,8 @@ const _D3MonitoringCharts = >({ const [yAxisRef, setYAxisRef] = useState(null) const [chartAreaRef, setChartAreaRef] = useState(null) const [axesAreaRef, setAxesAreaRef] = useState(null) - const [settingsVisible, setSettingsVisible] = useState(true) + const [settingsVisible, setSettingsVisible] = useState(false) - useEffect(() => { - invokeWebApiWrapperAsync( - async () => { - let sets = chartName ? await UserSettingsService.get(chartName) : null - if (typeof sets === 'string') - sets = JSON.parse(sets) - if (Array.isArray(sets)) { - setDatasets(sets) - } else if (Array.isArray(datasetGroups)) { - setDatasets(datasetGroups) - if (chartName) { - invokeWebApiWrapperAsync( - async () => await UserSettingsService.insert(chartName, datasetGroups), - undefined, - 'Не удалось сохранить настройки графиков' - ) - } - } - }, - undefined, - 'Не удалось загрузить настройки графиков' - ) - }, [datasetGroups, chartName]) - const offset = usePartialProps(_offset, defaultOffsets) const yTicks = usePartialProps>>(_yTicks, getDefaultYTicks) const yAxisConfig = usePartialProps>(_yAxisConfig, getDefaultYAxisConfig) @@ -258,7 +243,7 @@ const _D3MonitoringCharts = >({ domain = mm as Required } else if (data) { const [min, max] = d3.extent(data, chart.x) - domain = calculateDomain({ min, max, ...mm }, 100) + domain = calculateDomain({ min, max, ...mm }) } return [chart.key, { scale: d3.scaleLinear().domain([domain.min, domain.max]), @@ -268,7 +253,7 @@ const _D3MonitoringCharts = >({ out.forEach(([key], i) => { const chart = group.charts.find((chart) => chart.key === key) - const bind = chart?.bindDomainFrom + const bind = chart?.linkedTo if (!bind) return const bindDomain = out.find(([key]) => key === bind) if (bindDomain) @@ -284,7 +269,7 @@ const _D3MonitoringCharts = >({ key: i, charts: [], } - ), [chartArea, axesArea]) + ), [chartArea]) const onGroupsChange = useCallback((sets: ExtendedChartDataset[][]) => { if (chartName) { @@ -300,6 +285,44 @@ const _D3MonitoringCharts = >({ setSettingsVisible(false) }, [chartName]) + const onGroupsReset = useCallback(() => { + setSettingsVisible(false) + setDatasets(datasetGroups) + if (chartName) { + invokeWebApiWrapperAsync( + async () => await UserSettingsService.delete(chartName), + undefined, + 'Не удалось удалить настройки графиков' + ) + } + }, [datasetGroups, chartName]) + + useEffect(() => methods?.({ setSettingsVisible }), [methods]) + + useEffect(() => { + invokeWebApiWrapperAsync( + async () => { + let sets = chartName ? await UserSettingsService.get(chartName) : null + if (typeof sets === 'string') + sets = JSON.parse(sets) + if (Array.isArray(sets)) { + setDatasets(sets) + } else if (Array.isArray(datasetGroups)) { + setDatasets(datasetGroups) + if (chartName) { + invokeWebApiWrapperAsync( + async () => await UserSettingsService.insert(chartName, datasetGroups), + undefined, + 'Не удалось сохранить настройки графиков' + ) + } + } + }, + undefined, + 'Не удалось загрузить настройки графиков' + ) + }, [datasetGroups, chartName]) + useEffect(() => { if (isDev()) { datasets.forEach((sets, i) => { @@ -374,7 +397,7 @@ const _D3MonitoringCharts = >({ }, [yAxisConfig, chartArea, datasets, animDurationMs, createAxesGroup]) useEffect(() => { - const axesGroups = d3.select(axesAreaRef) + const axesGroups = axesArea() .selectAll('.charts-group') .data(groups) @@ -383,7 +406,7 @@ const _D3MonitoringCharts = >({ .append('g') .attr('class', 'charts-group') - const actualAxesGroups = d3.select(axesAreaRef) + const actualAxesGroups = axesArea() .selectAll>('.charts-group') .attr('class', (g) => `charts-group ${getGroupClass(g.key)}`) .attr('transform', (g) => `translate(${sizes.groupLeft(g.key)}, 0)`) @@ -436,7 +459,7 @@ const _D3MonitoringCharts = >({ .attr('stroke', j === chartsData.length - 1 ? 'gray' : 'currentColor') }) }) - }, [groups, sizes, spaceBetweenGroups, chartDomains]) + }, [groups, sizes, spaceBetweenGroups, chartDomains, axesArea]) useEffect(() => { // Рисуем ось Y if (!yAxis) return @@ -566,11 +589,11 @@ const _D3MonitoringCharts = >({ )} setSettingsVisible(false)} + onReset={onGroupsReset} /> diff --git a/src/components/d3/D3MonitoringGroupsEditor.tsx b/src/components/d3/D3MonitoringGroupsEditor.tsx index c392b58..25f9f13 100644 --- a/src/components/d3/D3MonitoringGroupsEditor.tsx +++ b/src/components/d3/D3MonitoringGroupsEditor.tsx @@ -1,5 +1,7 @@ import { CSSProperties, Key, memo, useCallback, useEffect, useMemo, useState } from 'react' -import { Divider, Empty, Modal, Tooltip, Tree } from 'antd' +import { Button, Divider, Empty, Modal, Popconfirm, Tooltip, Tree } from 'antd' +import { UndoOutlined } from '@ant-design/icons' +import { EventDataNode } from 'antd/lib/tree' import { getChartIcon } from '@utils' @@ -12,7 +14,7 @@ export type D3MonitoringGroupsEditorProps = { groups: ExtendedChartDataset[][] onChange: (value: ExtendedChartDataset[][]) => void onCancel: () => void - name?: string + onReset: () => void } const getChartLabel = (chart: ExtendedChartDataset) => ( @@ -29,7 +31,7 @@ const divStyle: CSSProperties = { flexGrow: 1, } -const getNodePos = (node: any): { group: number, chart?: number } => { +const getNodePos = (node: EventDataNode): { group: number, chart?: number } => { const out = node.pos.split('-').map(Number) return { group: out[1], chart: out[2] } } @@ -39,6 +41,7 @@ const _D3MonitoringGroupsEditor = ({ groups: oldGroups, onChange, onCancel, + onReset, }: D3MonitoringGroupsEditorProps) => { const [groups, setGroups] = useState[][]>([]) const [expand, setExpand] = useState([]) @@ -48,30 +51,37 @@ const _D3MonitoringGroupsEditor = ({ const onModalOk = useCallback(() => onChange(groups), [groups]) - const onDrop = useCallback((info: any) => { + const onDrop = useCallback((info: { + node: EventDataNode + dragNode: EventDataNode + dropPosition: number + }) => { const { dragNode, dropPosition, node } = info const targetNodes = getNodePos(node) const dragNodes = getNodePos(dragNode) const groupPos = dragNodes.group - if (!Number.isFinite(groupPos)) return + if (!Number.isFinite(groupPos + dropPosition)) return setGroups((prev) => { - if (typeof dragNodes.chart === 'undefined') { + const chartPos = dragNodes.chart + if (typeof chartPos === 'undefined') { const groups = [ ...prev ] - const movedGroups = groups.splice(dragNodes.group, 1) + const movedGroups = groups.splice(groupPos, 1) groups.splice(Math.max(dropPosition - 1, 0), 0, ...movedGroups) return groups - } else if (Number.isFinite(dragNodes.chart)) { - if (groupPos !== targetNodes.group) { - const dragKey = prev[dragNodes.group][dragNodes.chart].key - if (prev[targetNodes.group].find((chart) => chart.key === dragKey)) { + } else if (Number.isFinite(chartPos)) { + const targetGroup = targetNodes.group + const dragKey = prev[groupPos][chartPos].key + if (groupPos !== targetGroup) { + if (prev[targetGroup].find((chart) => chart.key === dragKey)) { notify('График с данным ключом уже существует в этой группе. Перемещение невозможно', 'warning') return prev } } const groups = [ ...prev ] - const charts = groups[groupPos].splice(dragNodes.chart, 1) - groups[targetNodes.group].splice(Math.max(dropPosition - 1, 0), 0, ...charts) + const charts = groups[groupPos].filter((chart) => chart.key === dragKey || chart.linkedTo === dragKey) + groups[groupPos] = groups[groupPos].filter((chart) => chart.key !== dragKey && chart.linkedTo !== dragKey) + groups[targetGroup].splice(Math.max(dropPosition - 1, 0), 0, ...charts) return groups } return prev @@ -98,11 +108,8 @@ const _D3MonitoringGroupsEditor = ({ return { group, chart } }, [selected]) - const selectedChart = useMemo(() => { - if (!selectedIdx) return null - - return groups[selectedIdx.group][selectedIdx.chart] - }, [groups, selectedIdx]) + const selectedGroup = useMemo(() => selectedIdx ? groups[selectedIdx.group] : null, [groups, selectedIdx]) + const selectedChart = useMemo(() => selectedIdx ? groups[selectedIdx.group][selectedIdx.chart] : null, [groups, selectedIdx]) const onChartChange = useCallback((chart: ExtendedChartDataset) => { if (!selectedIdx) return false @@ -119,10 +126,17 @@ const _D3MonitoringGroupsEditor = ({ centered width={800} visible={visible} - onOk={onModalOk} - onCancel={onCancel} - okText={'Сохранить изменения'} title={'Настройка групп графиков'} + onCancel={onCancel} + footer={( + <> + + + + + + + )} >
@@ -140,8 +154,8 @@ const _D3MonitoringGroupsEditor = ({
- {selectedChart ? ( - chart={selectedChart} onChange={onChartChange} /> + {selectedGroup && selectedChart ? ( + group={selectedGroup} chart={selectedChart} onChange={onChartChange} /> ) : ( )} diff --git a/src/components/d3/types.ts b/src/components/d3/types.ts index 6094803..5f7ae80 100644 --- a/src/components/d3/types.ts +++ b/src/components/d3/types.ts @@ -62,7 +62,7 @@ export type BaseChartDataset = { /** Параметры штриховки графика */ dash?: string | number | [string | number, string | number] /** Привязка домена к домену другого графика */ - bindDomainFrom?: string | number + linkedTo?: string | number } export type LineChartDataset = { diff --git a/src/pages/Telemetry/Archive/index.jsx b/src/pages/Telemetry/Archive/index.jsx index e01beb3..03b20b9 100755 --- a/src/pages/Telemetry/Archive/index.jsx +++ b/src/pages/Telemetry/Archive/index.jsx @@ -14,7 +14,7 @@ import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker' import { formatDate, range, wrapPrivateComponent } from '@utils' import { TelemetryDataSaubService } from '@api' -import { makeChartGroups, normalizeData } from '../TelemetryView' +import { makeChartGroups, normalizeData, yAxis } from '../TelemetryView' import cursorRender from '../TelemetryView/cursorRender' const DATA_COUNT = 2048 // Колличество точек на подгрузку графика @@ -247,6 +247,7 @@ const Archive = memo(() => { datasetGroups={chartGroups} data={chartData} yDomain={domain} + yAxis={yAxis} yTicks={{ visible: true, format: (d) => formatDate(d) diff --git a/src/pages/Telemetry/TelemetryView/index.jsx b/src/pages/Telemetry/TelemetryView/index.jsx index 8110c32..dca17a5 100755 --- a/src/pages/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Telemetry/TelemetryView/index.jsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback, memo, useMemo } from 'react' import { BehaviorSubject, buffer, throttleTime } from 'rxjs' -import { Select } from 'antd' +import { Button, Select } from 'antd' import { useIdWell } from '@asb/context' import { makeDateSorter } from '@components/Table' @@ -36,7 +36,7 @@ import '@styles/message.css' const { Option } = Select -const yAxis = { +export const yAxis = { type: 'time', accessor: (d) => new Date(d.date), format: (d) => formatDate(d, undefined, 'YYYY-MM-DD HH:mm:ss'), @@ -71,7 +71,7 @@ export const makeChartGroups = (flowChart) => { maxXAccessor: 'depthEnd', minYAccessor: accessor + 'Min', maxYAccessor: accessor + 'Max', - bindDomainFrom: accessor, + linkedTo: accessor, }) return [ @@ -148,6 +148,7 @@ const TelemetryView = memo(() => { const [flowChartData, setFlowChartData] = useState([]) const [rop, setRop] = useState(null) const [domain, setDomain] = useState({}) + const [chartMethods, setChartMethods] = useState() const idWell = useIdWell() @@ -271,6 +272,7 @@ const TelemetryView = memo(() => { Интервал: 
+
Статус: