Merged in feature/add-page-operation-time (pull request #10)

Доработка странице Наработка

Approved-by: Александр Васильевич Сироткин
This commit is contained in:
Салихов Тимур 2022-10-13 10:34:02 +00:00 committed by Александр Васильевич Сироткин
commit c9885e4603
4 changed files with 41 additions and 29 deletions

View File

@ -11,6 +11,7 @@ const { RangePicker } = DatePicker
export type DateRangeWrapperProps = RangePickerSharedProps<Moment> & {
value?: RangeValue<Moment>,
isUTC?: boolean
allowClear?: boolean
}
const normalizeDates = (value?: RangeValue<Moment>, isUTC?: boolean): RangeValue<Moment> => {
@ -21,10 +22,10 @@ const normalizeDates = (value?: RangeValue<Moment>, isUTC?: boolean): RangeValue
]
}
export const DateRangeWrapper = memo<DateRangeWrapperProps>(({ value, isUTC, ...other }) => (
export const DateRangeWrapper = memo<DateRangeWrapperProps>(({ value, isUTC, allowClear = false, ...other }) => (
<RangePicker
showTime
allowClear={false}
allowClear={allowClear}
format={defaultFormat}
defaultValue={[
moment().subtract(1, 'days').startOf('day'),

View File

@ -1,4 +1,4 @@
import { memo, useEffect, useMemo, useRef } from 'react'
import { memo, useCallback, useEffect, useMemo, useRef } from 'react'
import { useElementSize } from 'usehooks-ts'
import { Property } from 'csstype'
import * as d3 from 'd3'
@ -20,6 +20,7 @@ export type D3HorizontalChartProps = {
height?: Property.Height
data: PercentChartDataType[]
offset?: Partial<ChartOffset>
afterDraw?: (d: d3.Selection<SVGRectElement, PercentChartDataType, SVGGElement, unknown>) => void
}
const defaultOffset = { top: 50, right: 100, bottom: 50, left: 100 }
@ -29,13 +30,14 @@ export const D3HorizontalPercentChart = memo<D3HorizontalChartProps>(({
height: givenHeight = '100%',
offset: givenOffset,
data,
afterDraw,
}) => {
const offset = usePartialProps<ChartOffset>(givenOffset, defaultOffset)
const [divRef, { width, height }] = useElementSize()
const rootRef = useRef<SVGGElement | null>(null)
const root = useMemo(() => rootRef.current ? d3.select(rootRef.current) : null, [rootRef.current])
const root = useCallback(() => rootRef.current ? d3.select(rootRef.current) : null, [rootRef.current])
const inlineWidth = useMemo(() => width - offset.left - offset.right, [width])
const inlineHeight = useMemo(() => height - offset.top - offset.bottom, [height])
@ -44,42 +46,50 @@ export const D3HorizontalPercentChart = memo<D3HorizontalChartProps>(({
const yScale = useMemo(() => d3.scaleBand().domain(data.map((d) => d.name)).range([0, inlineHeight]).padding(0.25), [data, inlineHeight])
useEffect(() => { /// Отрисовываем оси X сверху и снизу
if (width < 100 || height < 100 || !root) return
const r = root()
if (width < 100 || height < 100 || !r) return
const xAxisTop = d3.axisTop(xScale).tickFormat((d) => `${d}%`).ticks(4).tickSize(-inlineHeight)
const xAxisBottom = d3.axisBottom(xScale).tickFormat((d) => `${d}%`).ticks(4)
root.selectChild<SVGGElement>('.axis.x.bottom').call(xAxisBottom)
root.selectChild<SVGGElement>('.axis.x.top').call(xAxisTop)
r.selectChild<SVGGElement>('.axis.x.bottom').call(xAxisBottom)
r.selectChild<SVGGElement>('.axis.x.top').call(xAxisTop)
.selectAll('.tick')
.attr('class', 'tick grid-line')
}, [root, width, height, xScale, inlineHeight])
useEffect(() => { /// Отрисовываем ось Y слева
if (width < 100 || height < 100 || !root) return
root.selectChild<SVGGElement>('.axis.y.left').call(d3.axisLeft(yScale))
const r = root()
if (width < 100 || height < 100 || !r) return
r.selectChild<SVGGElement>('.axis.y.left').call(d3.axisLeft(yScale))
}, [root, width, height, yScale])
useEffect(() => {
if (width < 100 || height < 100 || !root) return
const r = root()
if (width < 100 || height < 100 || !r) return
const delay = d3.transition().duration(500).ease(d3.easeLinear)
const rects = root.selectChild('.data').selectAll('rect').data(data)
const rects = r.selectChild('.data').selectAll('rect').data(data)
rects.enter().append('rect')
rects.exit().remove()
root.selectChild<SVGGElement>('.data')
const selectedRects = r.selectChild<SVGGElement>('.data')
.selectAll<SVGRectElement, PercentChartDataType>('rect')
.attr('fill', (d) => d.color || 'black')
selectedRects.attr('fill', (d) => d.color || 'black')
.attr('y', (d) => yScale(d.name) ?? null)
.attr('height', yScale.bandwidth())
.transition(delay)
.attr('width', (d) => xScale(d.percent))
}, [data, width, height, root, yScale, xScale])
.attr('width', (d) => d.percent > 0 ? xScale(d.percent) : 0)
afterDraw?.(selectedRects)
}, [data, width, height, root, yScale, xScale, afterDraw])
return (
<LoaderPortal show={false} style={{ width: givenWidth, height: givenHeight }}>
<div ref={divRef} style={{ width: '100%', height: '100%' }}>
<svg id={'d3-horizontal-chart'} width={'100%'} height={'100%'}>
<svg width={'100%'} height={'100%'}>
<g ref={rootRef} transform={`translate(${offset.left}, ${offset.top})`}>
<g className={'axis x top'}></g>
<g className={'axis x bottom'} transform={`translate(0, ${inlineHeight})`}></g>

View File

@ -22,14 +22,14 @@ const subsystemColors = [
]
const tableColumns = [
makeColumn('Цвет', 'color', { width: 50, render: (backgroundColor) => (
makeColumn('Цвет', 'color', { width: 60, render: (backgroundColor) => (
<div className={'table_color'} style={{ backgroundColor }} />
)}),
makeTextColumn('Подсистема', 'subsystemName'),
makeNumericColumn('Использование, %', 'kUsage', undefined, undefined, val => (+val * 100).toFixed(2)),
makeNumericColumn('Проходка, м', 'sumDepthInterval'),
makeNumericColumn('Время работы, ч', 'usedTimeHours'),
makeNumericColumn('Кол-во запусков', 'operationCount', undefined, undefined, makeNumericRender(0)),
makeNumericColumn('Использование, %', 'kUsage', undefined, undefined, val => (+val * 100).toFixed(2), 200),
makeNumericColumn('Проходка, м', 'sumDepthInterval', undefined, undefined, undefined, 200),
makeNumericColumn('Время работы, ч', 'usedTimeHours', undefined, undefined, undefined, 200),
makeNumericColumn('Кол-во запусков', 'operationCount', undefined, undefined, makeNumericRender(0), 200),
]
// Выбор доступен только до текущей даты
@ -65,15 +65,15 @@ export const OperationTime = memo(() => {
if (!well.id) return
// Ограничение задаётся только если выбраны обе даты
const startDate = dateRange[1] ? dateRange[0]?.toISOString() : undefined
const endDate = dateRange[1]?.toISOString()
const startDate = dateRange?.[1] ? dateRange[0]?.toISOString() : undefined
const endDate = dateRange?.[1] ? dateRange[1]?.toISOString() : undefined
const data = await SubsystemOperationTimeService.getStat(well.id, undefined, startDate, endDate)
// Выбираем цвета для подсистем (если цветов не хватает начинаем сначала)
const coloredData = arrayOrDefault(data).map((d, i) => ({ ...d, color: subsystemColors[i % subsystemColors.length] }))
setData(coloredData)
setSelected(data.map((d) => d.idSubsystem)) // По-умолчанию выбираем все подсистемы
setSelected(data?.map((d) => d.idSubsystem)) // По-умолчанию выбираем все подсистемы
},
setShowLoader,
`Не удалось загрузить данные`,
@ -95,6 +95,7 @@ export const OperationTime = memo(() => {
value={selected}
/>
<DateRangeWrapper
allowClear
onCalendarChange={setDateRange}
disabledDate={disabledDates}
disabledTime={disabledTimes}