From bc22769ecf5f546eb892a203051fdc671485f3da Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Tue, 16 Mar 2021 22:45:26 +0800 Subject: [PATCH] TransferRateGraph: migrate to FC --- .../components/sidebar/TransferData.tsx | 33 +- .../sidebar/TransferRateDetails.tsx | 13 +- .../components/sidebar/TransferRateGraph.tsx | 292 +++++------------- 3 files changed, 97 insertions(+), 241 deletions(-) diff --git a/client/src/javascript/components/sidebar/TransferData.tsx b/client/src/javascript/components/sidebar/TransferData.tsx index 3fe5e951..17d5e26a 100644 --- a/client/src/javascript/components/sidebar/TransferData.tsx +++ b/client/src/javascript/components/sidebar/TransferData.tsx @@ -1,32 +1,17 @@ -import {FC, useState, useRef, useEffect} from 'react'; +import {FC, useState, useRef} from 'react'; import Measure from 'react-measure'; import {observer} from 'mobx-react'; -import {reaction} from 'mobx'; import ClientStatusStore from '../../stores/ClientStatusStore'; -import TransferDataStore from '../../stores/TransferDataStore'; import TransferRateDetails from './TransferRateDetails'; import TransferRateGraph from './TransferRateGraph'; -import type {TransferRateGraphInspectorPoint} from './TransferRateGraph'; +import type {TransferRateGraphEventHandlers, TransferRateGraphInspectorPoint} from './TransferRateGraph'; const TransferData: FC = observer(() => { const [graphInspectorPoint, setGraphInspectorPoint] = useState(null); const [sidebarWidth, setSidebarWidth] = useState(0); - const rateGraphRef = useRef(null); - - useEffect(() => { - const dispose = reaction( - () => TransferDataStore.transferRates, - () => { - if (rateGraphRef.current != null) { - rateGraphRef.current.handleTransferHistoryChange(); - } - }, - ); - - return dispose; - }, []); + const rateGraphHandlerRefs = useRef(null); return ( { className="client-stats" onMouseMove={(event) => { if (event?.nativeEvent?.clientX != null) { - rateGraphRef.current?.handleMouseMove(event.nativeEvent.clientX); + rateGraphHandlerRefs.current?.handleMouseMove(event.nativeEvent.clientX); } }} - onMouseOver={() => rateGraphRef.current?.handleMouseOver()} - onMouseOut={() => rateGraphRef.current?.handleMouseOut()} - onFocus={() => rateGraphRef.current?.handleMouseOver()} - onBlur={() => rateGraphRef.current?.handleMouseOut()}> + onMouseOver={() => rateGraphHandlerRefs.current?.handleMouseOver()} + onMouseOut={() => rateGraphHandlerRefs.current?.handleMouseOut()} + onFocus={() => rateGraphHandlerRefs.current?.handleMouseOver()} + onBlur={() => rateGraphHandlerRefs.current?.handleMouseOut()}> {ClientStatusStore.isConnected && ( { onHover={(inspectorPoint) => { setGraphInspectorPoint(inspectorPoint); }} - ref={rateGraphRef} + handlerRefs={rateGraphHandlerRefs} width={sidebarWidth} /> )} diff --git a/client/src/javascript/components/sidebar/TransferRateDetails.tsx b/client/src/javascript/components/sidebar/TransferRateDetails.tsx index f68738b3..ec1cec6a 100644 --- a/client/src/javascript/components/sidebar/TransferRateDetails.tsx +++ b/client/src/javascript/components/sidebar/TransferRateDetails.tsx @@ -22,7 +22,7 @@ const icons = { }; interface TransferRateDetailsProps { - inspectorPoint: TransferRateGraphInspectorPoint | null; + inspectorPoint?: TransferRateGraphInspectorPoint | null; } const TransferRateDetails: FC = observer(({inspectorPoint}: TransferRateDetailsProps) => { @@ -49,8 +49,8 @@ const TransferRateDetails: FC = observer(({inspectorPo if (inspectorPoint != null) { transferRates = { - upload: inspectorPoint.uploadSpeed, - download: inspectorPoint.downloadSpeed, + upload: inspectorPoint.upload, + download: inspectorPoint.download, }; } @@ -63,13 +63,10 @@ const TransferRateDetails: FC = observer(({inspectorPo }); let timestamp = null; - if (inspectorPoint?.nearestTimestamp != null) { + if (inspectorPoint?.timestamp != null) { timestamp = (
- +
); } diff --git a/client/src/javascript/components/sidebar/TransferRateGraph.tsx b/client/src/javascript/components/sidebar/TransferRateGraph.tsx index eb26354d..cadfe7fa 100644 --- a/client/src/javascript/components/sidebar/TransferRateGraph.tsx +++ b/client/src/javascript/components/sidebar/TransferRateGraph.tsx @@ -1,10 +1,10 @@ import {area, curveMonotoneX, line} from 'd3-shape'; -import {Component, FC, ReactNode} from 'react'; +import {FC, MutableRefObject, useRef, useState} from 'react'; import {max} from 'd3-array'; -import {ScaleLinear, scaleLinear} from 'd3-scale'; -import {Selection, select} from 'd3-selection'; +import {observer} from 'mobx-react'; +import {scaleLinear} from 'd3-scale'; -import type {TransferDirection} from '@shared/types/TransferData'; +import type {TransferDirection, TransferHistory} from '@shared/types/TransferData'; import TransferDataStore, {TRANSFER_DIRECTIONS} from '../../stores/TransferDataStore'; @@ -15,244 +15,118 @@ const TransferRateGraphGradient: FC<{direction: TransferDirection}> = ({directio ); +export interface TransferRateGraphEventHandlers { + handleMouseMove: (mouseX: number) => void; + handleMouseOut: () => void; + handleMouseOver: () => void; +} + export interface TransferRateGraphInspectorPoint { - uploadSpeed: number; - downloadSpeed: number; - nearestTimestamp: number; + upload: number; + download: number; + timestamp: number; } interface TransferRateGraphProps { id: string; height: number; width: number; + handlerRefs: MutableRefObject; onHover: (inspectorPoint: TransferRateGraphInspectorPoint) => void; onMouseOut: () => void; } -class TransferRateGraph extends Component { - lastMouseX?: number; - xScale?: ScaleLinear; - yScale?: ScaleLinear; - graphRefs: { - graph: SVGSVGElement | null; - areDefined: boolean; - isHovered: boolean; - } & Record< - TransferDirection, - { - graphArea?: Selection; - inspectPoint?: Selection; - rateLine?: Selection; - } - > = { - graph: null, - areDefined: false, - isHovered: false, - download: {}, - upload: {}, - }; +const getSpeedAtHoverPoint = (history: TransferHistory, direction: TransferDirection, hoverPoint: number) => { + const upperSpeed = history[direction][Math.ceil(hoverPoint)]; + const lowerSpeed = history[direction][Math.floor(hoverPoint)]; + return lowerSpeed + (upperSpeed - lowerSpeed) * (hoverPoint % 1); +}; - static defaultProps = { - width: 240, - }; +const TransferRateGraph: FC = observer( + ({id, height, width, handlerRefs, onHover, onMouseOut}: TransferRateGraphProps) => { + const [isHovered, setIsHovered] = useState(false); - componentDidMount(): void { - this.renderGraphData(); - } - - componentDidUpdate(): void { - this.renderGraphData(); - } - - private setInspectorCoordinates(slug: TransferDirection, hoverPoint: number): number { - const { - graphRefs: { - [slug]: {inspectPoint}, - }, - xScale, - yScale, - } = this; - - if (xScale == null || yScale == null || inspectPoint == null) { - return 0; - } + const hoverPoint = useRef(0); + const inspectorPoint = useRef({download: 0, upload: 0, timestamp: 0}); const historicalData = TransferDataStore.transferRates; - const upperSpeed = historicalData[slug][Math.ceil(hoverPoint)]; - const lowerSpeed = historicalData[slug][Math.floor(hoverPoint)]; - const delta = upperSpeed - lowerSpeed; - const speedAtHoverPoint = lowerSpeed + delta * (hoverPoint % 1); - - const coordinates = {x: xScale(hoverPoint), y: yScale(speedAtHoverPoint)}; - - inspectPoint.attr('transform', `translate(${coordinates.x},${coordinates.y})`); - - return speedAtHoverPoint; - } - - handleMouseMove = (mouseX: number): void => { - this.lastMouseX = mouseX; - this.renderPrecisePointInspectors(); - }; - - handleMouseOut = (): void => { - const {graphRefs, props} = this; - - graphRefs.isHovered = false; - - TRANSFER_DIRECTIONS.forEach((direction: T) => { - const {inspectPoint} = graphRefs[direction]; - if (inspectPoint != null) { - graphRefs[direction].inspectPoint = inspectPoint.style('opacity', 0); - } - }); - - if (props.onMouseOut) { - props.onMouseOut(); - } - }; - - handleMouseOver = (): void => { - this.graphRefs.isHovered = true; - - TRANSFER_DIRECTIONS.forEach((direction: T) => { - const {inspectPoint} = this.graphRefs[direction]; - if (inspectPoint != null) { - this.graphRefs[direction].inspectPoint = inspectPoint.style('opacity', 1); - } - }); - }; - - handleTransferHistoryChange = (): void => { - this.updateGraph(); - }; - - private initGraph(): void { - if (this.graphRefs.areDefined === true) { - return; - } - - const {id} = this.props; - - const graph = select(`#${id}`); - TRANSFER_DIRECTIONS.forEach((direction: T) => { - // appendEmptyGraphShapes - this.graphRefs[direction].graphArea = graph - .append('path') - .attr('class', 'graph__area') - .attr('fill', `url('#graph__gradient--${direction}')`); - - // appendEmptyGraphLines - this.graphRefs[direction].rateLine = graph.append('path').attr('class', `graph__line graph__line--${direction}`); - - // appendGraphCircles - this.graphRefs[direction].inspectPoint = graph - .append('circle') - .attr('class', `graph__circle graph__circle--${direction}`) - .attr('r', 2.5); - }); - - this.graphRefs.areDefined = true; - } - - private updateGraph() { - this.renderGraphData(); - - if (this.graphRefs.isHovered) { - this.renderPrecisePointInspectors(); - } - } - - private renderPrecisePointInspectors(): void { - const { - lastMouseX, - props: {onHover}, - xScale, - } = this; - - if (xScale == null || lastMouseX == null) { - return; - } - - const historicalData = TransferDataStore.transferRates; - const hoverPoint = xScale.invert(lastMouseX); - const uploadSpeed = this.setInspectorCoordinates('upload', hoverPoint); - const downloadSpeed = this.setInspectorCoordinates('download', hoverPoint); - const nearestTimestamp = historicalData.timestamps[Math.round(hoverPoint)]; - - if (onHover) { - onHover({ - uploadSpeed, - downloadSpeed, - nearestTimestamp, - }); - } - } - - private renderGraphData(): void { - const historicalData = TransferDataStore.transferRates; - const {height, width} = this.props; - const margin = {bottom: 10, top: 10}; - - this.xScale = scaleLinear() + const xScale = scaleLinear() .domain([0, historicalData.download.length - 1]) .range([0, width]); - this.yScale = scaleLinear() + const yScale = scaleLinear() .domain([ 0, max(historicalData.download, (dataPoint, index) => Math.max(dataPoint, historicalData.upload[index])) as number, ]) - .range([height - margin.top, margin.bottom]); + .range([height - 10, 10]); - this.initGraph(); + // eslint-disable-next-line no-param-reassign + handlerRefs.current = { + handleMouseMove: (mouseX: number) => { + hoverPoint.current = xScale.invert(mouseX); + onHover(inspectorPoint.current); + }, + handleMouseOut: () => { + setIsHovered(false); + onMouseOut(); + }, + handleMouseOver: () => { + setIsHovered(true); + }, + }; + + if (isHovered) { + inspectorPoint.current = { + download: getSpeedAtHoverPoint(historicalData, 'download', hoverPoint.current), + upload: getSpeedAtHoverPoint(historicalData, 'upload', hoverPoint.current), + timestamp: historicalData.timestamps[Math.round(hoverPoint.current)], + }; + } const interpolation = curveMonotoneX; - TRANSFER_DIRECTIONS.forEach((direction: T) => { - const {xScale, yScale} = this; - const {graphArea, rateLine} = this.graphRefs[direction]; - - if (rateLine == null || graphArea == null || xScale == null || yScale == null) { - return; - } - - this.graphRefs[direction].rateLine = rateLine.attr( - 'd', - line() - .x((_dataPoint, index) => xScale(index) || 0) - .y((dataPoint) => yScale(dataPoint) || 0) - .curve(interpolation)(historicalData[direction]) as string, - ); - - this.graphRefs[direction].graphArea = graphArea.attr( - 'd', - area() - .x((_dataPoint, index) => xScale(index) || 0) - .y0(height) - .y1((dataPoint) => yScale(dataPoint) || 0) - .curve(interpolation)(historicalData[direction]) as string, - ); - }); - } - - render(): ReactNode { - const {id} = this.props; return ( - { - this.graphRefs.graph = ref; - }}> + + {TRANSFER_DIRECTIONS.map((direction) => [ + () + .x((_dataPoint, index) => xScale(index) || 0) + .y0(height) + .y1((dataPoint) => yScale(dataPoint) || 0) + .curve(interpolation)(historicalData[direction]) as string + } + />, + () + .x((_dataPoint, index) => xScale(index) || 0) + .y((dataPoint) => yScale(dataPoint) || 0) + .curve(interpolation)(historicalData[direction]) as string + } + />, + , + ])} ); - } -} + }, +); export default TransferRateGraph;