Add time range support to charts

This commit is contained in:
Thomas Nordquist
2019-07-07 23:54:28 +02:00
parent 0ff6359a41
commit 77dcbccd5c
3 changed files with 48 additions and 23 deletions

View File

@@ -1,7 +1,6 @@
import * as q from '../../../../backend/src/Model' import * as q from '../../../../backend/src/Model'
import * as React from 'react' import React, { useState } from 'react'
import TopicPlot from '../TopicPlot' import TopicPlot from '../TopicPlot'
import { AppState } from '../../reducers'
import { bindActionCreators } from 'redux' import { bindActionCreators } from 'redux'
import { chartActions } from '../../actions' import { chartActions } from '../../actions'
import { ChartParameters } from '../../reducers/Charts' import { ChartParameters } from '../../reducers/Charts'
@@ -20,32 +19,48 @@ interface Props {
} }
/** /**
* Subscribes to onMessage of treeNode * Subscribes to onMessages keeping track of additional data points
*/ */
function useMessageSubscriptionToUpdate(treeNode?: q.TreeNode<any>) { function useMessageSubscriptionToUpdate(treeNode?: q.TreeNode<any>) {
const [lastUpdated, setLastUpdate] = React.useState(0) const [lastUpdated, setLastUpdate] = useState(0)
const [messageHistory, setMessageHistory] = useState<q.MessageHistory | undefined>()
let amendMessageCallback: any
function subscribeToMessageUpdates() { function subscribeToMessageUpdates() {
const onUpdateCallback = throttle(() => setLastUpdate(treeNode ? treeNode.lastUpdate : 0), 300) const throttledUpdate = throttle(() => setLastUpdate(treeNode ? treeNode.lastUpdate : 0), 300)
treeNode && treeNode.onMessage.subscribe(onUpdateCallback)
if (treeNode) {
const newMessageHistory = treeNode.messageHistory.clone()
newMessageHistory.setCapacity(500, 2 * 500 * 10000)
amendMessageCallback = (message: q.Message) => {
newMessageHistory.add(message)
throttledUpdate()
}
treeNode.onMessage.subscribe(amendMessageCallback)
setMessageHistory(newMessageHistory)
}
return function cleanup() { return function cleanup() {
treeNode && treeNode.onMessage.unsubscribe(onUpdateCallback) treeNode && treeNode.onMessage.unsubscribe(amendMessageCallback)
} }
} }
React.useEffect(subscribeToMessageUpdates, [treeNode]) React.useEffect(subscribeToMessageUpdates, [treeNode])
return messageHistory
} }
function Chart(props: Props) { function Chart(props: Props) {
const { parameters, treeNode } = props const { parameters, treeNode } = props
const [freezedHistory, setHistory] = React.useState<q.MessageHistory | undefined>() const [frozenHistory, setFrozenHistory] = React.useState<q.MessageHistory | undefined>()
useMessageSubscriptionToUpdate(treeNode) const messageHistory = useMessageSubscriptionToUpdate(treeNode)
const togglePause = React.useCallback(() => { const togglePause = React.useCallback(() => {
if (!props.treeNode) { if (!props.treeNode) {
return return
} }
setHistory(freezedHistory ? undefined : props.treeNode.messageHistory.clone()) setFrozenHistory(frozenHistory ? undefined : messageHistory && messageHistory.clone())
}, [props.treeNode, freezedHistory]) }, [props.treeNode, frozenHistory])
const onRemove = React.useCallback(() => { const onRemove = React.useCallback(() => {
props.actions.chart.removeChart(props.parameters) props.actions.chart.removeChart(props.parameters)
@@ -63,18 +78,18 @@ function Chart(props: Props) {
<ChartActions <ChartActions
parameters={parameters} parameters={parameters}
onRemove={onRemove} onRemove={onRemove}
paused={Boolean(freezedHistory)} paused={Boolean(frozenHistory)}
togglePause={togglePause} togglePause={togglePause}
/> />
</div> </div>
</div> </div>
{props.treeNode ? ( {messageHistory ? (
<TopicPlot <TopicPlot
color={props.parameters.color} color={props.parameters.color}
interpolation={props.parameters.interpolation} interpolation={props.parameters.interpolation}
timeInterval={props.parameters.timeRange ? props.parameters.timeRange.until : undefined} timeInterval={props.parameters.timeRange ? props.parameters.timeRange.until : undefined}
range={props.parameters.range ? [props.parameters.range.from, props.parameters.range.to] : undefined} range={props.parameters.range ? [props.parameters.range.from, props.parameters.range.to] : undefined}
history={freezedHistory ? freezedHistory : props.treeNode.messageHistory} history={frozenHistory || messageHistory}
dotPath={parameters.dotPath} dotPath={parameters.dotPath}
/> />
) : ( ) : (

View File

@@ -4,7 +4,6 @@ import { Button, Menu, TextField, Typography } from '@material-ui/core'
import { chartActions } from '../../../actions' import { chartActions } from '../../../actions'
import { ChartParameters } from '../../../reducers/Charts' import { ChartParameters } from '../../../reducers/Charts'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { KeyCodes } from '../../../utils/KeyCodes'
const parseDuration = require('parse-duration') const parseDuration = require('parse-duration')
interface Props { interface Props {
@@ -22,13 +21,16 @@ function TimeRangeSettings(props: Props) {
) )
const ranges = ['all', '10s', '30s', '1m', '5m', '15m', '1h', '6h', '1d'] const ranges = ['all', '10s', '30s', '1m', '5m', '15m', '1h', '6h', '1d']
const manuallySetIntervalHandler = useCallback( const manuallySetIntervalHandler = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
(e: React.KeyboardEvent<HTMLInputElement>) => setValue(e.currentTarget.value), setValue(e.target.value)
[] }, [])
)
useEffect(() => { useEffect(() => {
if (!value) { if (!value) {
props.actions.chart.updateChart({
...props.chart,
timeRange: undefined,
})
return return
} }
@@ -54,8 +56,8 @@ function TimeRangeSettings(props: Props) {
open={props.open} open={props.open}
onClose={props.onClose} onClose={props.onClose}
> >
<Typography>Discard values older then</Typography> <Typography>Chart data within a time interval</Typography>
<div style={{ padding: '0 16px', width: '300px', textAlign: 'center' }}> <div style={{ padding: '0 16px', width: '275px', textAlign: 'center' }}>
{ranges.map(r => { {ranges.map(r => {
return ( return (
<Button <Button
@@ -69,13 +71,16 @@ function TimeRangeSettings(props: Props) {
) )
})} })}
</div> </div>
<Typography style={{ fontSize: '0.75em' }}>
<i>Limited to 500 data points</i>
</Typography>
<br /> <br />
<TextField <TextField
style={{ marginLeft: '8px', marginTop: '0' }} style={{ marginLeft: '8px', marginTop: '0' }}
onClick={dismissClick} onClick={dismissClick}
label="interval" label="custom interval"
value={value || ''} value={value || ''}
onKeyPress={manuallySetIntervalHandler} onChange={manuallySetIntervalHandler}
margin="normal" margin="normal"
/> />
</Menu> </Menu>

View File

@@ -54,6 +54,11 @@ export class RingBuffer<T extends Lengthwise> {
this.usage -= freedSpace this.usage -= freedSpace
} }
public setCapacity(items: number, bytes: number) {
this.maxItems = items
this.capacity = bytes
}
public clone(): RingBuffer<T> { public clone(): RingBuffer<T> {
return new RingBuffer(this.capacity, this.maxItems, this) return new RingBuffer(this.capacity, this.maxItems, this)
} }