Add time range support to charts
This commit is contained in:
@@ -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}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user