diff --git a/app/src/components/ChartPanel/ChartActions.tsx b/app/src/components/ChartPanel/ChartActions.tsx index c10b77c..e83ffb4 100644 --- a/app/src/components/ChartPanel/ChartActions.tsx +++ b/app/src/components/ChartPanel/ChartActions.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import React, { useRef } from 'react' import Play from '@material-ui/icons/PlayArrow' import Pause from '@material-ui/icons/PauseCircleFilled' import Clear from '@material-ui/icons/Clear' @@ -11,16 +11,24 @@ export function ChartActions(props: { togglePause: () => void parameters: ChartParameters onRemove: () => void + resetDataAction: () => void }) { + const menuAnchor = useRef() return (
{props.paused ? : } - + +
+ {/* Helper element to provide an anchor element for the menu, + * so the menu prefers not to overlap with the chart + */} +
+
) } diff --git a/app/src/components/ChartPanel/ChartSettings/MoveUp.tsx b/app/src/components/ChartPanel/ChartSettings/MoveUp.tsx index 9b542f9..f1db8c5 100644 --- a/app/src/components/ChartPanel/ChartSettings/MoveUp.tsx +++ b/app/src/components/ChartPanel/ChartSettings/MoveUp.tsx @@ -1,9 +1,10 @@ import * as React from 'react' -import { ChartParameters } from '../../../reducers/Charts' -import { Menu, MenuItem, TextField, Typography } from '@material-ui/core' -import { connect } from 'react-redux' +import ArrowUpward from '@material-ui/icons/ArrowUpward' import { bindActionCreators } from 'redux' import { chartActions } from '../../../actions' +import { ChartParameters } from '../../../reducers/Charts' +import { connect } from 'react-redux' +import { MenuItem, Typography, ListItemIcon } from '@material-ui/core' function MoveUp(props: { actions: { chart: typeof chartActions }; chart: ChartParameters; close: () => void }) { const moveUp = React.useCallback(() => { @@ -16,7 +17,10 @@ function MoveUp(props: { actions: { chart: typeof chartActions }; chart: ChartPa return ( - Move up + + + + Move up ) } diff --git a/app/src/components/ChartPanel/ChartSettings/RangeSettings.tsx b/app/src/components/ChartPanel/ChartSettings/RangeSettings.tsx index 034e33c..042b40b 100644 --- a/app/src/components/ChartPanel/ChartSettings/RangeSettings.tsx +++ b/app/src/components/ChartPanel/ChartSettings/RangeSettings.tsx @@ -56,8 +56,8 @@ function RangeSettings(props: Props) { onClose={props.onClose} onKeyDownCapture={handleKeyEvents} > - Define custom ranges for the Y-Axis -
+
+ Define custom ranges for the Y-Axis void + menuAnchor: React.MutableRefObject +}) { const [visible, setVisible] = React.useState(false) - const settingsRef = React.useRef() const toggleSettings = React.useCallback(() => { setVisible(!visible) }, [visible]) @@ -17,10 +20,15 @@ export function SettingsButton(props: { parameters: ChartParameters }) { return ( - + diff --git a/app/src/components/ChartPanel/ChartSettings/Size.tsx b/app/src/components/ChartPanel/ChartSettings/Size.tsx index bbcfa0f..66796fd 100644 --- a/app/src/components/ChartPanel/ChartSettings/Size.tsx +++ b/app/src/components/ChartPanel/ChartSettings/Size.tsx @@ -24,16 +24,16 @@ function Size(props: { return ( - auto + auto - 100% width + 100% width - 50% width + 50% width - 33% width + 33% width ) diff --git a/app/src/components/ChartPanel/ChartSettings/index.tsx b/app/src/components/ChartPanel/ChartSettings/index.tsx index 5d8501a..4858eff 100644 --- a/app/src/components/ChartPanel/ChartSettings/index.tsx +++ b/app/src/components/ChartPanel/ChartSettings/index.tsx @@ -1,16 +1,22 @@ -import * as React from 'react' +import BarChart from '@material-ui/icons/BarChart' +import Clear from '@material-ui/icons/Refresh' +import ColorLens from '@material-ui/icons/ColorLens' import ColorSettings from './ColorSettings' import InterpolationSettings from './InterpolationSettings' import MoveUp from './MoveUp' +import MultiLineChart from '@material-ui/icons/MultiLineChart' import RangeSettings from './RangeSettings' +import React, { memo } from 'react' import Size from './Size' +import Sort from '@material-ui/icons/Sort' import TimeRangeSettings from './TimeRangeSettings' import { ChartParameters } from '../../../reducers/Charts' -import { Menu, MenuItem } from '@material-ui/core' +import { Menu, MenuItem, ListItemIcon, Typography } from '@material-ui/core' function ChartSettings(props: { open: boolean close: () => void + resetDataAction: () => void chart: ChartParameters anchorEl: React.MutableRefObject }) { @@ -59,19 +65,40 @@ function ChartSettings(props: { - Set range + + + + Y-Axis range (Values) - Time range + + + + X-Axis range (Time) + + + {' '} Curve interpolation - Size + + + + Size - Color + + + + Color + + + + + + Clear data @@ -94,4 +121,4 @@ function ChartSettings(props: { ) } -export default ChartSettings +export default memo(ChartSettings) diff --git a/app/src/components/ChartPanel/TopicChart.tsx b/app/src/components/ChartPanel/TopicChart.tsx index 689be60..8fbba9c 100644 --- a/app/src/components/ChartPanel/TopicChart.tsx +++ b/app/src/components/ChartPanel/TopicChart.tsx @@ -1,16 +1,31 @@ import * as q from '../../../../backend/src/Model' -import React, { useState } from 'react' +import ChartTitle from './ChartTitle' +import React, { useState, useCallback, memo, useRef } from 'react' import TopicPlot from '../TopicPlot' import { bindActionCreators } from 'redux' +import { ChartActions } from './ChartActions' import { chartActions } from '../../actions' import { ChartParameters } from '../../reducers/Charts' import { connect } from 'react-redux' import { Paper } from '@material-ui/core' -import ChartTitle from './ChartTitle' -import { ChartActions } from './ChartActions' -import { RingBuffer } from '../../../../backend/src/Model' const throttle = require('lodash.throttle') +class ClearableMessageBuffer extends q.RingBuffer { + public clear() { + this.items = [] + this.start = 0 + this.end = 0 + } + + public static fromMessageBuffer(buffer: q.RingBuffer): ClearableMessageBuffer { + return new ClearableMessageBuffer(buffer.capacity, buffer.maxItems, buffer.compactionFactor, buffer) + } + + public clone(): ClearableMessageBuffer { + return ClearableMessageBuffer.fromMessageBuffer(this) + } +} + interface Props { parameters: ChartParameters treeNode?: q.TreeNode @@ -24,14 +39,14 @@ interface Props { */ function useMessageSubscriptionToUpdate(treeNode?: q.TreeNode) { const [lastUpdated, setLastUpdate] = useState(0) - const [messageHistory, setMessageHistory] = useState() + const [messageHistory, setMessageHistory] = useState() let amendMessageCallback: any function subscribeToMessageUpdates() { const throttledUpdate = throttle(() => setLastUpdate(treeNode ? treeNode.lastUpdate : 0), 300) if (treeNode) { - const newMessageHistory = treeNode.messageHistory.clone() + const newMessageHistory = ClearableMessageBuffer.fromMessageBuffer(treeNode.messageHistory) newMessageHistory.setCapacity(500, 2 * 500 * 10000) amendMessageCallback = (message: q.Message) => { @@ -52,12 +67,21 @@ function useMessageSubscriptionToUpdate(treeNode?: q.TreeNode) { return messageHistory } +function useResetDataCallback(messageHistory: ClearableMessageBuffer | undefined) { + const [lastUpdated, setLastUpdate] = useState(0) + + return React.useCallback(() => { + messageHistory && messageHistory.clear() + setLastUpdate(Date.now()) + }, [messageHistory]) +} + function TopicChart(props: Props) { const { parameters, treeNode } = props - const [frozenHistory, setFrozenHistory] = React.useState() + const [frozenHistory, setFrozenHistory] = useState() const messageHistory = useMessageSubscriptionToUpdate(treeNode) - const togglePause = React.useCallback(() => { + const togglePause = useCallback(() => { if (!treeNode) { return } @@ -69,6 +93,8 @@ function TopicChart(props: Props) { props.actions.chart.removeChart(props.parameters) }, [props.parameters]) + const resetData = useResetDataCallback(messageHistory) + return ( (1)} + history={frozenHistory || messageHistory || new ClearableMessageBuffer(1)} dotPath={parameters.dotPath} /> @@ -109,4 +136,4 @@ const mapDispatchToProps = (dispatch: any) => { export default connect( undefined, mapDispatchToProps -)(TopicChart) +)(memo(TopicChart)) diff --git a/app/src/components/ConfirmationDialog.tsx b/app/src/components/ConfirmationDialog.tsx index d1972a3..2db7397 100644 --- a/app/src/components/ConfirmationDialog.tsx +++ b/app/src/components/ConfirmationDialog.tsx @@ -7,7 +7,7 @@ function ConfirmationDialog(props: { confirmationRequests: Array() const noRef = useRef() - const arrowKeyHandler = useCallback((event: KeyboardEvent) => { + const arrowKeyHandler = useCallback((event: React.KeyboardEvent) => { const isArrowKey = event.keyCode === KeyCodes.arrow_left || event.keyCode === KeyCodes.arrow_right if (!isArrowKey) { return @@ -45,10 +45,10 @@ function ConfirmationDialog(props: { confirmationRequests: Array{request.inquiry} - - diff --git a/backend/src/Model/RingBuffer.ts b/backend/src/Model/RingBuffer.ts index 727662b..d36960a 100644 --- a/backend/src/Model/RingBuffer.ts +++ b/backend/src/Model/RingBuffer.ts @@ -1,15 +1,15 @@ -interface Lengthwise { +export interface MemoryConsumptionExpressedByLength { length: number } -export class RingBuffer { - private capacity: number - private maxItems: number +export class RingBuffer { + public capacity: number + public maxItems: number + public compactionFactor: number + protected items: Array = [] + protected start: number = 0 + protected end: number = 0 private usage: number = 0 - private items: Array = [] - private start: number = 0 - private end: number = 0 - private compactionFactor: number constructor(capacity: number, maxItems = Infinity, compactionFactor: number = 10, ringBuffer?: RingBuffer) { this.capacity = capacity diff --git a/backend/src/Model/TreeNode.ts b/backend/src/Model/TreeNode.ts index 296a248..7d8b6fe 100644 --- a/backend/src/Model/TreeNode.ts +++ b/backend/src/Model/TreeNode.ts @@ -52,10 +52,6 @@ export class TreeNode { return this.sourceEdge ? this.sourceEdge.source || undefined : undefined } - public hasMessage() { - return this.message && this.message.value && this.message.value.length !== 0 - } - private isTopicEmptyLeaf() { return !this.hasMessage() && this.isLeaf() } @@ -108,6 +104,10 @@ export class TreeNode { } } + public hasMessage() { + return this.message && this.message.value && this.message.value.length !== 0 + } + public destroy() { this.onDestroy.dispatch(this) this.onDestroy.removeAllListeners()