Add time range setting for charts

This commit is contained in:
Thomas Nordquist
2019-07-07 21:38:03 +02:00
parent 5830d99d45
commit 195dcf37d4
8 changed files with 163 additions and 14 deletions

View File

@@ -72,6 +72,7 @@ function Chart(props: Props) {
<TopicPlot
color={props.parameters.color}
interpolation={props.parameters.interpolation}
timeInterval={props.parameters.timeRange ? props.parameters.timeRange.until : undefined}
range={props.parameters.range ? [props.parameters.range.from, props.parameters.range.to] : undefined}
history={freezedHistory ? freezedHistory : props.treeNode.messageHistory}
dotPath={parameters.dotPath}

View File

@@ -0,0 +1,97 @@
import React, { ChangeEvent, MouseEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { bindActionCreators } from 'redux'
import { Button, Menu, TextField, Typography } from '@material-ui/core'
import { chartActions } from '../../../actions'
import { ChartParameters } from '../../../reducers/Charts'
import { connect } from 'react-redux'
import { KeyCodes } from '../../../utils/KeyCodes'
const parseDuration = require('parse-duration')
interface Props {
actions: { chart: typeof chartActions }
chart: ChartParameters
anchorEl?: Element
open: boolean
onClose: () => void
}
function TimeRangeSettings(props: Props) {
const dismissClick = useCallback((e: MouseEvent) => e.stopPropagation(), [])
const [value, setValue] = useState<string | undefined>(
props.chart.timeRange ? props.chart.timeRange.until : undefined
)
const ranges = ['all', '10s', '30s', '1m', '5m', '15m', '1h', '6h', '1d']
const manuallySetIntervalHandler = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => setValue(e.currentTarget.value),
[]
)
useEffect(() => {
if (!value) {
return
}
const canBeParsed = Boolean(parseDuration(value))
if (canBeParsed) {
props.actions.chart.updateChart({
...props.chart,
timeRange: {
until: value,
},
})
}
}, [value])
return useMemo(() => {
const createRangeHandler = (range: string) => (e: React.MouseEvent) => setValue(range === 'all' ? undefined : range)
return (
<Menu
style={{ textAlign: 'center' }}
keepMounted={true}
anchorEl={props.anchorEl}
open={props.open}
onClose={props.onClose}
>
<Typography>Discard values older then</Typography>
<div style={{ padding: '0 16px', width: '300px', textAlign: 'center' }}>
{ranges.map(r => {
return (
<Button
style={{ margin: '4px', textTransform: 'none' }}
variant="contained"
key={r}
onClick={createRangeHandler(r)}
>
{r}
</Button>
)
})}
</div>
<br />
<TextField
style={{ marginLeft: '8px', marginTop: '0' }}
onClick={dismissClick}
label="interval"
value={value || ''}
onKeyPress={manuallySetIntervalHandler}
margin="normal"
/>
</Menu>
)
}, [value, props.open])
}
const mapDispatchToProps = (dispatch: any) => {
return {
actions: {
chart: bindActionCreators(chartActions, dispatch),
},
}
}
export default connect(
undefined,
mapDispatchToProps
)(TimeRangeSettings)

View File

@@ -1,11 +1,12 @@
import * as React from 'react'
import ColorSettings from './ColorSettings'
import InterpolationSettings from './InterpolationSettings'
import { ChartParameters } from '../../../reducers/Charts'
import { Menu, MenuItem } from '@material-ui/core'
import MoveUp from './MoveUp'
import RangeSettings from './RangeSettings'
import Size from './Size'
import MoveUp from './MoveUp'
import ColorSettings from './ColorSettings'
import TimeRangeSettings from './TimeRangeSettings'
import { ChartParameters } from '../../../reducers/Charts'
import { Menu, MenuItem } from '@material-ui/core'
function ChartSettings(props: {
open: boolean
@@ -14,6 +15,7 @@ function ChartSettings(props: {
anchorEl: React.MutableRefObject<undefined>
}) {
const [rangeVisible, setRangeVisible] = React.useState(false)
const [timeRangeVisible, setTimeRangeVisible] = React.useState(false)
const [interpolationVisible, setInterpolationVisible] = React.useState(false)
const [sizeVisible, setSizeVisible] = React.useState(false)
const [colorVisible, setColorVisible] = React.useState(false)
@@ -25,6 +27,13 @@ function ChartSettings(props: {
setRangeVisible(!rangeVisible)
}, [rangeVisible, open])
const toggleTimeRange = React.useCallback(() => {
if (open) {
props.close()
}
setTimeRangeVisible(!timeRangeVisible)
}, [timeRangeVisible, open])
const toggleInterpolation = React.useCallback(() => {
if (open) {
props.close()
@@ -52,6 +61,9 @@ function ChartSettings(props: {
<MenuItem key="range" onClick={toggleRange}>
Set range
</MenuItem>
<MenuItem key="timeRange" onClick={toggleTimeRange}>
Time range
</MenuItem>
<MenuItem key="interpolation" onClick={toggleInterpolation}>
Curve interpolation
</MenuItem>
@@ -64,6 +76,12 @@ function ChartSettings(props: {
<MoveUp chart={props.chart} close={props.close} />
</Menu>
<RangeSettings chart={props.chart} anchorEl={props.anchorEl.current} open={rangeVisible} onClose={toggleRange} />
<TimeRangeSettings
chart={props.chart}
anchorEl={props.anchorEl.current}
open={timeRangeVisible}
onClose={toggleTimeRange}
/>
<InterpolationSettings
chart={props.chart}
anchorEl={props.anchorEl.current}

View File

@@ -12,6 +12,7 @@ interface Props {
theme: Theme
interpolation?: PlotCurveTypes
range?: [number?, number?]
timeRangeStart?: number
color?: string
}
@@ -55,10 +56,12 @@ export default withTheme((props: Props) => {
return React.useMemo(() => {
const data = props.data
const calculatedDomain = domainForData(data)
let yDomain: [number, number] = props.range
const yDomain: [number, number] = props.range
? [props.range[0] || calculatedDomain[0], props.range[1] || calculatedDomain[1]]
: calculatedDomain
const xDomain = props.timeRangeStart ? [Date.now() - props.timeRangeStart, Date.now()] : undefined
let color: string =
props.theme.palette.type === 'light' ? props.theme.palette.secondary.dark : props.theme.palette.primary.light
if (props.color) {
@@ -67,7 +70,7 @@ export default withTheme((props: Props) => {
return (
<div style={{ height: '150px', overflow: 'hidden' }}>
<XYPlot width={width} height={180} yDomain={yDomain}>
<XYPlot width={width} height={180} yDomain={yDomain} xDomain={xDomain}>
<HorizontalGridLines />
<XAxis />
<YAxis width={45} tickFormat={(num: number) => abbreviate(num)} />

View File

@@ -5,18 +5,28 @@ import PlotHistory from './Sidebar/PlotHistory'
import { Base64Message } from '../../../backend/src/Model/Base64Message'
import { toPlottableValue } from './Sidebar/CodeDiff/util'
import { PlotCurveTypes } from '../reducers/Charts'
const parseDuration = require('parse-duration')
interface Props {
history: q.MessageHistory
dotPath?: string
timeInterval?: string
interpolation?: PlotCurveTypes
range?: [number?, number?]
color?: string
}
function nodeToHistory(history: q.MessageHistory) {
return history
.toArray()
function filterUsingTimeRange(startTime: number | undefined, data: Array<q.Message>) {
if (startTime) {
const threshold = new Date(Date.now() - startTime)
return data.filter(d => d.received >= threshold)
}
return data
}
function nodeToHistory(startTime: number | undefined, history: q.MessageHistory) {
return filterUsingTimeRange(startTime, history.toArray())
.map((message: q.Message) => {
const value = message.value ? toPlottableValue(Base64Message.toUnicodeString(message.value)) : NaN
return { x: message.received.getTime(), y: toPlottableValue(value) }
@@ -24,9 +34,8 @@ function nodeToHistory(history: q.MessageHistory) {
.filter(data => !isNaN(data.y as any)) as any
}
function nodeDotPathToHistory(history: q.MessageHistory, dotPath: string) {
return history
.toArray()
function nodeDotPathToHistory(startTime: number | undefined, history: q.MessageHistory, dotPath: string) {
return filterUsingTimeRange(startTime, history.toArray())
.map((message: q.Message) => {
let json = {}
try {
@@ -41,8 +50,20 @@ function nodeDotPathToHistory(history: q.MessageHistory, dotPath: string) {
}
function render(props: Props) {
const data = props.dotPath ? nodeDotPathToHistory(props.history, props.dotPath) : nodeToHistory(props.history)
return <PlotHistory color={props.color} range={props.range} interpolation={props.interpolation} data={data} />
const startOffset = props.timeInterval ? parseDuration(props.timeInterval) : undefined
const data = props.dotPath
? nodeDotPathToHistory(startOffset, props.history, props.dotPath)
: nodeToHistory(startOffset, props.history)
return (
<PlotHistory
timeRangeStart={startOffset}
color={props.color}
range={props.range}
interpolation={props.interpolation}
data={data}
/>
)
}
export default render

View File

@@ -13,6 +13,9 @@ export interface ChartParameters {
from?: number
to?: number
}
timeRange?: {
until: string
}
width?: 'big' | 'medium' | 'small'
color?: string
}