forked from ddrilling/asb_cloud_front
Скорректирования работа редактирования и сохранения настроек графиков
This commit is contained in:
parent
15333510fd
commit
a8613e6b75
@ -1,4 +1,4 @@
|
||||
import { Button, Form, FormItemProps, Input, InputNumber, Select, Space, Tooltip, Typography } from 'antd'
|
||||
import { Button, Form, FormItemProps, Input, InputNumber, Select, Tooltip } from 'antd'
|
||||
import { CSSProperties, memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { ColorPicker, Color } from '../ColorPicker'
|
||||
@ -17,87 +17,68 @@ const lineTypes = [
|
||||
{ value: 'needle', label: 'Иглы' },
|
||||
]
|
||||
|
||||
export type D3MonitoringChartEditorProps<DataType> = Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, 'onChange'> & {
|
||||
chart?: ExtendedChartDataset<DataType> | null
|
||||
export type D3MonitoringChartEditorProps<DataType> = {
|
||||
chart: ExtendedChartDataset<DataType>
|
||||
onChange: (value: ExtendedChartDataset<DataType>) => boolean
|
||||
}
|
||||
|
||||
const _D3MonitoringChartEditor = <DataType,>({
|
||||
chart: value,
|
||||
onChange,
|
||||
|
||||
style,
|
||||
...other
|
||||
}: D3MonitoringChartEditorProps<DataType>) => {
|
||||
const [domain, setDomain] = useState<MinMax>({})
|
||||
const [color, setColor] = useState<Color>()
|
||||
|
||||
const [form] = Form.useForm()
|
||||
|
||||
const onDomainChange = useCallback((mm: MinMax) => {
|
||||
setDomain((prev) => ({
|
||||
min: ('min' in mm) ? mm.min : prev?.min,
|
||||
max: ('max' in mm) ? mm.max : prev?.max,
|
||||
}))
|
||||
}, [])
|
||||
|
||||
const onColorChange = useCallback((color: Color) => setColor((prev) => color ?? prev), [])
|
||||
|
||||
useEffect(() => {
|
||||
if (value?.type) {
|
||||
form.setFieldsValue(value)
|
||||
} else {
|
||||
form.resetFields()
|
||||
}
|
||||
setColor(value?.color ? new Color(String(value.color)) : undefined)
|
||||
setDomain(value?.xDomain ?? {})
|
||||
}, [value, form])
|
||||
|
||||
const onSave = useCallback(() => {
|
||||
if (!value) return
|
||||
const onSave = useCallback((props: Partial<ExtendedChartDataset<DataType>>) => {
|
||||
const values = form.getFieldsValue()
|
||||
const newValue = {
|
||||
...value,
|
||||
color,
|
||||
xDomain: domain,
|
||||
...values,
|
||||
...props
|
||||
}
|
||||
onChange(newValue)
|
||||
}, [form, domain, color, value])
|
||||
}, [value])
|
||||
|
||||
const divStyle: CSSProperties = useMemo(() => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
...style,
|
||||
}), [style])
|
||||
const onDomainChange = useCallback((mm: MinMax) => {
|
||||
onSave({ xDomain: {
|
||||
min: ('min' in mm) ? mm.min : value.xDomain?.min,
|
||||
max: ('max' in mm) ? mm.max : value.xDomain?.max,
|
||||
}})
|
||||
}, [value])
|
||||
|
||||
const onColorChange = useCallback((color: Color) => {
|
||||
onSave({ color: color.toHexString() })
|
||||
}, [value])
|
||||
|
||||
useEffect(() => {
|
||||
if (value.type)
|
||||
form.setFieldsValue(value)
|
||||
else
|
||||
form.resetFields()
|
||||
}, [value, form])
|
||||
|
||||
return (
|
||||
<div style={divStyle} {...other}>
|
||||
<Form form={form} onChange={onSave}>
|
||||
<Tooltip title={'Возможность изменения типов линий будет добавлена в будущих обновлениях'}>
|
||||
<Item label={'Тип'}><Select disabled value={value?.type ?? 'Неизвестный'} options={lineTypes} /></Item>
|
||||
</Tooltip>
|
||||
<Item label={'Название'}>
|
||||
<Input.Group compact>
|
||||
<Item name={'label'} rules={[{ required: true }]}><Input placeholder={'Полное'} /></Item>
|
||||
<Item name={'shortLabel'}><Input placeholder={'Краткое'}/></Item>
|
||||
</Input.Group>
|
||||
</Item>
|
||||
<Item label={'Диапазон'}>
|
||||
<Input.Group compact>
|
||||
<Item><InputNumber value={domain?.min} onChange={(min) => onDomainChange({ min })} placeholder={'Мин'} /></Item>
|
||||
<Item><InputNumber value={domain?.max} onChange={(max) => onDomainChange({ max })} placeholder={'Макс'} /></Item>
|
||||
<Button
|
||||
disabled={!domain?.min && !domain?.max}
|
||||
onClick={() => onDomainChange({ min: undefined, max: undefined })}
|
||||
>Авто</Button>
|
||||
</Input.Group>
|
||||
</Item>
|
||||
<Item label={'Цвет линий'}><ColorPicker onChange={onColorChange} value={color} /></Item>
|
||||
</Form>
|
||||
</div>
|
||||
<Form form={form}>
|
||||
<Tooltip title={'Возможность изменения типов линий будет добавлена в будущих обновлениях'}>
|
||||
<Item label={'Тип'}><Select disabled value={value.type ?? 'Неизвестный'} options={lineTypes} /></Item>
|
||||
</Tooltip>
|
||||
<Item label={'Название'}>
|
||||
<Input.Group compact>
|
||||
<Item name={'label'} rules={[{ required: true }]}><Input placeholder={'Полное'} onChange={(e) => onSave({ label: e.target.value })} /></Item>
|
||||
<Item name={'shortLabel'}><Input placeholder={'Краткое'} onChange={(e) => onSave({ shortLabel: e.target.value })} /></Item>
|
||||
</Input.Group>
|
||||
</Item>
|
||||
<Item label={'Диапазон'}>
|
||||
<Input.Group compact>
|
||||
<InputNumber value={value.xDomain?.min} onChange={(min) => onDomainChange({ min })} placeholder={'Мин'} />
|
||||
<InputNumber value={value.xDomain?.max} onChange={(max) => onDomainChange({ max })} placeholder={'Макс'} />
|
||||
<Button
|
||||
disabled={!Number.isFinite(value.xDomain?.min) && !Number.isFinite(value.xDomain?.max)}
|
||||
onClick={() => onDomainChange({ min: undefined, max: undefined })}
|
||||
>Авто</Button>
|
||||
</Input.Group>
|
||||
</Item>
|
||||
<Item label={'Цвет линий'}><ColorPicker onChange={onColorChange} value={value.color} /></Item>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -184,31 +184,27 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
|
||||
const [settingsVisible, setSettingsVisible] = useState<boolean>(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (!chartName) {
|
||||
setDatasets(datasetGroups)
|
||||
return
|
||||
}
|
||||
let datasets: ExtendedChartDataset<DataType>[][] = []
|
||||
let needInsert = false
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const sets = await UserSettingsService.get(chartName)
|
||||
needInsert = !sets
|
||||
datasets = sets ?? datasetGroups
|
||||
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,
|
||||
'Не удалось загрузить настройки графиков'
|
||||
)
|
||||
setDatasets(datasets)
|
||||
if (needInsert) {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
await UserSettingsService.insert(chartName, datasets)
|
||||
},
|
||||
undefined,
|
||||
'Не удалось сохранить настройки графиков'
|
||||
)
|
||||
}
|
||||
}, [datasetGroups, chartName])
|
||||
|
||||
const offset = usePartialProps(_offset, defaultOffsets)
|
||||
@ -242,7 +238,7 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
|
||||
groupLeft: (i: number) => (groupWidth + spaceBetweenGroups) * i,
|
||||
axisTop: (i: number, count: number) => axisHeight * (maxChartCount - count + i + 1)
|
||||
})
|
||||
}, [groups, height, offset])
|
||||
}, [groups, width, height, offset, axisHeight, spaceBetweenGroups])
|
||||
|
||||
const yAxis = useMemo(() => {
|
||||
if (!data) return
|
||||
@ -258,7 +254,7 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
|
||||
const out: [string | number, ChartDomain][] = group.charts.map((chart) => {
|
||||
const mm = { ...chart.xDomain }
|
||||
let domain: Required<MinMax> = { min: 0, max: 100 }
|
||||
if (mm.min && mm.max) {
|
||||
if (!Number.isNaN((mm.min ?? NaN) + (mm.max ?? NaN))) {
|
||||
domain = mm as Required<MinMax>
|
||||
} else if (data) {
|
||||
const [min, max] = d3.extent(data, chart.x)
|
||||
@ -291,9 +287,18 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
|
||||
), [chartArea, axesArea])
|
||||
|
||||
const onGroupsChange = useCallback((sets: ExtendedChartDataset<DataType>[][]) => {
|
||||
if (chartName) {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
await UserSettingsService.update(chartName, sets)
|
||||
},
|
||||
undefined,
|
||||
'Не удалось сохранить параметры графиков'
|
||||
)
|
||||
}
|
||||
setDatasets(sets)
|
||||
setSettingsVisible(false)
|
||||
}, [])
|
||||
}, [chartName])
|
||||
|
||||
useEffect(() => {
|
||||
if (isDev()) {
|
||||
@ -345,7 +350,7 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
|
||||
animDurationMs,
|
||||
...dataset,
|
||||
yAxis: dataset.yAxis ?? yAxisConfig,
|
||||
y: getByAccessor(dataset.yAxis.accessor ?? yAxisConfig.accessor),
|
||||
y: getByAccessor(dataset.yAxis?.accessor ?? yAxisConfig.accessor),
|
||||
x: getByAccessor(dataset.xAxis?.accessor),
|
||||
}
|
||||
)
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { Key, memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Divider, Modal, Tooltip, Tree } from 'antd'
|
||||
import { CSSProperties, Key, memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Divider, Empty, Modal, Tooltip, Tree } from 'antd'
|
||||
|
||||
import { getChartIcon } from '@utils'
|
||||
|
||||
import { ChartGroup, ExtendedChartDataset } from './D3MonitoringCharts'
|
||||
import { ExtendedChartDataset } from './D3MonitoringCharts'
|
||||
import D3MonitoringChartEditor from './D3MonitoringChartEditor'
|
||||
import { notify } from '../factory'
|
||||
|
||||
export type D3MonitoringGroupsEditorProps<DataType> = {
|
||||
visible?: boolean
|
||||
@ -14,30 +15,30 @@ export type D3MonitoringGroupsEditorProps<DataType> = {
|
||||
name?: string
|
||||
}
|
||||
|
||||
const moveToPos = <T,>(arr: T[], pos: number, newPos: number): T[] => {
|
||||
if (pos === newPos) return arr
|
||||
if (newPos === -1) return [arr[pos], ...arr.slice(0, pos), ...arr.slice(pos + 1)]
|
||||
if (newPos === arr.length) return [...arr.slice(0, pos), ...arr.slice(pos + 1), arr[pos]]
|
||||
const newArray = []
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (i == newPos) newArray.push(arr[pos])
|
||||
if (i !== pos) newArray.push(arr[i])
|
||||
}
|
||||
return newArray
|
||||
}
|
||||
|
||||
const getChartLabel = <DataType,>(chart: ExtendedChartDataset<DataType>) => (
|
||||
<Tooltip title={chart.label}>
|
||||
{getChartIcon(chart)} {chart.label}
|
||||
</Tooltip>
|
||||
)
|
||||
)
|
||||
|
||||
const divStyle: CSSProperties = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexGrow: 1,
|
||||
}
|
||||
|
||||
const getNodePos = (node: any): { group: number, chart?: number } => {
|
||||
const out = node.pos.split('-').map(Number)
|
||||
return { group: out[1], chart: out[2] }
|
||||
}
|
||||
|
||||
const _D3MonitoringGroupsEditor = <DataType,>({
|
||||
visible,
|
||||
groups: oldGroups,
|
||||
onChange,
|
||||
onCancel,
|
||||
name,
|
||||
}: D3MonitoringGroupsEditorProps<DataType>) => {
|
||||
const [groups, setGroups] = useState<ExtendedChartDataset<DataType>[][]>([])
|
||||
const [expand, setExpand] = useState<Key[]>([])
|
||||
@ -48,22 +49,30 @@ const _D3MonitoringGroupsEditor = <DataType,>({
|
||||
const onModalOk = useCallback(() => onChange(groups), [groups])
|
||||
|
||||
const onDrop = useCallback((info: any) => {
|
||||
const { dragNode, dropPosition, dropToGap } = info
|
||||
const { dragNode, dropPosition, node } = info
|
||||
|
||||
const nodes = dragNode.pos.split('-')
|
||||
const groupPos = Number(nodes[1])
|
||||
const targetNodes = getNodePos(node)
|
||||
const dragNodes = getNodePos(dragNode)
|
||||
const groupPos = dragNodes.group
|
||||
if (!Number.isFinite(groupPos)) return
|
||||
setGroups((prev) => {
|
||||
if (dropToGap) {
|
||||
if (nodes.length < 3)
|
||||
return moveToPos(prev, groupPos, dropPosition)
|
||||
} else {
|
||||
if (nodes.length < 3) return prev
|
||||
const chartPos = Number(nodes[2])
|
||||
if (Number.isFinite(chartPos)) {
|
||||
prev[groupPos] = moveToPos(prev[groupPos], chartPos, dropPosition)
|
||||
return [ ...prev ]
|
||||
if (typeof dragNodes.chart === 'undefined') {
|
||||
const groups = [ ...prev ]
|
||||
const movedGroups = groups.splice(dragNodes.group, 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)) {
|
||||
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)
|
||||
return groups
|
||||
}
|
||||
return prev
|
||||
})
|
||||
@ -108,7 +117,7 @@ const _D3MonitoringGroupsEditor = <DataType,>({
|
||||
return (
|
||||
<Modal
|
||||
centered
|
||||
width={700}
|
||||
width={800}
|
||||
visible={visible}
|
||||
onOk={onModalOk}
|
||||
onCancel={onCancel}
|
||||
@ -116,9 +125,9 @@ const _D3MonitoringGroupsEditor = <DataType,>({
|
||||
title={'Настройка групп графиков'}
|
||||
>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', height: 250 }}>
|
||||
<div style={{ width: '25%' }}>
|
||||
<div style={{ width: '35%' }}>
|
||||
<Tree
|
||||
// draggable
|
||||
draggable
|
||||
selectable
|
||||
onExpand={(keys) => setExpand(keys)}
|
||||
expandedKeys={expand}
|
||||
@ -130,7 +139,13 @@ const _D3MonitoringGroupsEditor = <DataType,>({
|
||||
/>
|
||||
</div>
|
||||
<Divider type={'vertical'} style={{ height: '100%', padding: '0 5px' }} />
|
||||
<D3MonitoringChartEditor<DataType> chart={selectedChart} style={{ flexGrow: 1 }} onChange={onChartChange} />
|
||||
<div style={divStyle}>
|
||||
{selectedChart ? (
|
||||
<D3MonitoringChartEditor<DataType> chart={selectedChart} onChange={onChartChange} />
|
||||
) : (
|
||||
<Empty description={'Выберите график для редактирования'} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CSSProperties, ReactNode, SVGProps, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { CSSProperties, ReactNode, SVGProps, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import * as d3 from 'd3'
|
||||
|
||||
import { useD3MouseZone } from '@components/d3/D3MouseZone'
|
||||
@ -104,7 +104,7 @@ const _D3HorizontalCursor = <DataType,>({
|
||||
if (!unsubscribe() && isDev())
|
||||
console.warn('Не удалось отвязать эвент')
|
||||
}
|
||||
}, [subscribe])
|
||||
}, [subscribe, fixed, mouseState.visible])
|
||||
|
||||
useEffect(() => {
|
||||
if (!zone || !getXLine) return
|
||||
|
@ -110,7 +110,7 @@ function _D3Tooltip<DataType extends Record<string, unknown>>({
|
||||
if (!unsubscribe() && isDev())
|
||||
console.warn('Не удалось отвязать эвент')
|
||||
}
|
||||
}, [visible])
|
||||
}, [subscribe, visible])
|
||||
|
||||
useEffect(() => {
|
||||
if (!tooltipRef.current || !zoneRect || fixed) return
|
||||
|
@ -49,7 +49,6 @@ const makeDataset = (label, shortLabel, color, key, unit, other) => ({
|
||||
label,
|
||||
shortLabel,
|
||||
color,
|
||||
yAxis,
|
||||
xAxis: {
|
||||
type: 'linear',
|
||||
accessor: key,
|
||||
@ -171,7 +170,7 @@ const TelemetryView = memo(() => {
|
||||
useEffect(() => {
|
||||
const subscribtion = saubSubject$.pipe(
|
||||
buffer(saubSubject$.pipe(throttleTime(700)))
|
||||
).subscribe((data) => handleDataSaub(data.flat()))
|
||||
).subscribe((data) => handleDataSaub(data.flat().filter(Boolean)))
|
||||
|
||||
return () => subscribtion.unsubscribe()
|
||||
}, [saubSubject$])
|
||||
@ -179,7 +178,7 @@ const TelemetryView = memo(() => {
|
||||
useEffect(() => {
|
||||
const subscribtion = spinSubject$.pipe(
|
||||
buffer(spinSubject$.pipe(throttleTime(700)))
|
||||
).subscribe((data) => handleDataSpin(data.flat()))
|
||||
).subscribe((data) => handleDataSpin(data.flat().filter(Boolean)))
|
||||
|
||||
return () => subscribtion.unsubscribe()
|
||||
}, [spinSubject$])
|
||||
@ -294,9 +293,11 @@ const TelemetryView = memo(() => {
|
||||
</GridItem>
|
||||
<GridItem col={2} row={2} colSpan={8} rowSpan={2}>
|
||||
<D3MonitoringCharts
|
||||
chartName={'monitoring'}
|
||||
datasetGroups={chartGroups}
|
||||
data={filteredData}
|
||||
yDomain={domain}
|
||||
yAxis={yAxis}
|
||||
yTicks={{
|
||||
visible: true,
|
||||
format: (d) => formatDate(d, 'YYYY-MM-DD')
|
||||
|
Loading…
Reference in New Issue
Block a user