Add numeric chart panel
This commit is contained in:
107
app/src/components/ChartPanel/Chart.tsx
Normal file
107
app/src/components/ChartPanel/Chart.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
import * as React from 'react'
|
||||
import Clear from '@material-ui/icons/Clear'
|
||||
import CustomIconButton from '../helper/CustomIconButton'
|
||||
import TopicPlot from '../TopicPlot'
|
||||
import { AppState } from '../../reducers'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { chartActions } from '../../actions'
|
||||
import { ChartParameters } from '../../reducers/Charts'
|
||||
import { connect } from 'react-redux'
|
||||
import { Paper, Theme, Typography, withStyles, Fade } from '@material-ui/core'
|
||||
|
||||
interface Props {
|
||||
parameters: ChartParameters
|
||||
tree?: q.Tree<any>
|
||||
classes: any
|
||||
actions: {
|
||||
chart: typeof chartActions
|
||||
}
|
||||
}
|
||||
|
||||
function Chart(props: Props) {
|
||||
if (!props.tree) {
|
||||
return null
|
||||
}
|
||||
|
||||
const { tree, parameters } = props
|
||||
const initialTreeNode = tree.findNode(parameters.topic)
|
||||
const [treeNode, setTreeNode] = React.useState<q.TreeNode<any> | undefined>(initialTreeNode)
|
||||
const [lastUpdate, setLastUpdate] = React.useState(0)
|
||||
|
||||
/** If a node is not available when the plot is shown, keep polling until it has been created */
|
||||
function pollForTreeNode() {
|
||||
const onUpdateCallback = () => setLastUpdate(treeNode ? treeNode.lastUpdate : 0)
|
||||
let intervalTimer: any
|
||||
|
||||
if (!treeNode) {
|
||||
intervalTimer = setInterval(() => {
|
||||
const node = tree.findNode(parameters.topic)
|
||||
if (node) {
|
||||
setTreeNode(node)
|
||||
node.onMessage.subscribe(onUpdateCallback)
|
||||
clearInterval(intervalTimer)
|
||||
}
|
||||
}, 500)
|
||||
} else {
|
||||
treeNode.onMessage.subscribe(onUpdateCallback)
|
||||
}
|
||||
|
||||
return function cleanup() {
|
||||
treeNode && treeNode.onMessage.unsubscribe(onUpdateCallback)
|
||||
intervalTimer && clearInterval(intervalTimer)
|
||||
}
|
||||
}
|
||||
React.useEffect(pollForTreeNode)
|
||||
|
||||
const onClick = React.useCallback(() => {
|
||||
props.actions.chart.removeChart(props.parameters)
|
||||
}, [props.parameters])
|
||||
|
||||
return (
|
||||
<Paper style={{ padding: '8px' }}>
|
||||
<div style={{ float: 'right' }}>
|
||||
<CustomIconButton tooltip="Remove chart" onClick={onClick}>
|
||||
<Clear />
|
||||
</CustomIconButton>
|
||||
</div>
|
||||
<Typography variant="caption" className={props.classes.topic}>
|
||||
{parameters.dotPath ? parameters.dotPath : ''}
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography variant="caption" className={props.classes.topic}>
|
||||
{parameters.topic}
|
||||
</Typography>
|
||||
<br />
|
||||
{treeNode ? <TopicPlot history={treeNode.messageHistory} dotPath={parameters.dotPath} /> : <span>No data</span>}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
tree: state.tree.get('tree'),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch: any) => {
|
||||
return {
|
||||
actions: {
|
||||
chart: bindActionCreators(chartActions, dispatch),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => ({
|
||||
topic: {
|
||||
wordBreak: 'break-all' as 'break-all',
|
||||
whiteSpace: 'nowrap' as 'nowrap',
|
||||
overflow: 'hidden' as 'hidden',
|
||||
textOverflow: 'ellipsis' as 'ellipsis',
|
||||
},
|
||||
})
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withStyles(styles)(Chart))
|
||||
124
app/src/components/ChartPanel/index.tsx
Normal file
124
app/src/components/ChartPanel/index.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import * as React from 'react'
|
||||
import Chart from './Chart'
|
||||
import ShowChart from '@material-ui/icons/ShowChart'
|
||||
import { AppState } from '../../reducers'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { chartActions } from '../../actions'
|
||||
import { ChartParameters } from '../../reducers/Charts'
|
||||
import { connect } from 'react-redux'
|
||||
import { Grid, Theme, Typography, withStyles } from '@material-ui/core'
|
||||
import { List } from 'immutable'
|
||||
const { TransitionGroup, CSSTransition } = require('react-transition-group/esm')
|
||||
|
||||
interface Props {
|
||||
charts: List<ChartParameters>
|
||||
connectionId?: string
|
||||
actions: {
|
||||
chart: typeof chartActions
|
||||
}
|
||||
}
|
||||
|
||||
function spacingForChartCount(count: number): 4 | 6 | 12 {
|
||||
if (count >= 5) {
|
||||
return 4
|
||||
} else if (count >= 2) {
|
||||
return 6
|
||||
} else {
|
||||
return 12
|
||||
}
|
||||
}
|
||||
|
||||
// function FadingChart(props: { chartParameters: ChartParameters; chartsInView: number; key: any }) {
|
||||
// const { chartsInView, chartParameters } = props
|
||||
// const [spacing, setSpacing] = React.useState(spacingForChartCount(chartsInView))
|
||||
|
||||
// // Update spacing after animations have completed
|
||||
// React.useEffect(() => {
|
||||
// const newSpacing = spacingForChartCount(chartsInView)
|
||||
// if (spacing !== newSpacing) {
|
||||
// setSpacing(newSpacing)
|
||||
// // setTimeout(() => , 500)
|
||||
// }
|
||||
// })
|
||||
|
||||
// return (
|
||||
|
||||
// )
|
||||
// }
|
||||
|
||||
function ChartPanel(props: Props) {
|
||||
const chartsInView = props.charts.count()
|
||||
|
||||
const [spacing, setSpacing] = React.useState(spacingForChartCount(chartsInView))
|
||||
|
||||
React.useEffect(() => {
|
||||
props.actions.chart.loadCharts()
|
||||
}, [props.connectionId])
|
||||
|
||||
// Update spacing after animations have completed
|
||||
React.useEffect(() => {
|
||||
const newSpacing = spacingForChartCount(chartsInView)
|
||||
if (newSpacing > spacing) {
|
||||
setTimeout(() => setSpacing(newSpacing), 500)
|
||||
} else {
|
||||
setSpacing(newSpacing)
|
||||
}
|
||||
}, [chartsInView])
|
||||
|
||||
const charts = props.charts.map(chartParameters => (
|
||||
<CSSTransition
|
||||
key={`${chartParameters.topic}-${chartParameters.dotPath || ''}`}
|
||||
timeout={{ enter: 500, exit: 500 }}
|
||||
classNames="example"
|
||||
>
|
||||
<Grid item xs={spacing}>
|
||||
<Chart parameters={chartParameters} />
|
||||
</Grid>
|
||||
</CSSTransition>
|
||||
))
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%', height: '100%', padding: '8px', borderTop: '1px solid #999' }}>
|
||||
<Grid container spacing={1}>
|
||||
<TransitionGroup component={null} className="example">
|
||||
{charts}
|
||||
</TransitionGroup>
|
||||
{chartsInView === 0 ? <NoCharts key="noCharts" /> : null}
|
||||
</Grid>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function NoCharts() {
|
||||
return (
|
||||
<div style={{ width: '100%', textAlign: 'center' }}>
|
||||
<Typography variant="h2">No charts selected</Typography>
|
||||
<Typography>Select a numeric values from the value preview.</Typography>
|
||||
<Typography>
|
||||
Click on <ShowChart /> to add a topic / value to this panel.
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
charts: state.charts.get('charts'),
|
||||
connectionId: state.connection.connectionId,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch: any) => {
|
||||
return {
|
||||
actions: {
|
||||
chart: bindActionCreators(chartActions, dispatch),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => ({})
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withStyles(styles)(ChartPanel))
|
||||
Reference in New Issue
Block a user