Add numeric chart panel

This commit is contained in:
Thomas Nordquist
2019-06-16 19:10:37 +02:00
parent 4ec8cdf0ff
commit 209899c3b8
28 changed files with 719 additions and 219 deletions

View File

@@ -0,0 +1,82 @@
import * as q from '../../../../../backend/src/Model'
import * as React from 'react'
import ShowChart from '@material-ui/icons/ShowChart'
import TopicPlot from '../../TopicPlot'
import { bindActionCreators } from 'redux'
import { chartActions } from '../../../actions'
import { connect } from 'react-redux'
import { Fade, Paper, Popper, Tooltip } from '@material-ui/core'
import { JsonPropertyLocation } from '../../../../../backend/src/JsonAstParser'
interface Props {
treeNode: q.TreeNode<any>
classes: any
literal: JsonPropertyLocation
actions: {
chart: typeof chartActions
}
}
function ChartPreview(props: Props) {
const chartIconRef = React.useRef(null)
const [open, setOpen] = React.useState(false)
const onClick = React.useCallback(() => {
props.actions.chart.addChart({
topic: props.treeNode.path(),
dotPath: props.literal.path,
})
}, [props.literal.path, props.treeNode])
const mouseOver = React.useCallback(() => {
setOpen(true)
}, [])
const mouseOut = React.useCallback(() => {
setOpen(false)
}, [])
const hasEnoughDataToDisplayDiagrams = props.treeNode.messageHistory.count() > 1
let preview = hasEnoughDataToDisplayDiagrams ? (
<Tooltip title="Click to add to chart panel">
<ShowChart
ref={chartIconRef}
className={props.classes.icon}
onMouseEnter={mouseOver}
onMouseLeave={mouseOut}
onClick={onClick}
/>
</Tooltip>
) : (
<Tooltip title="Click to add to chart panel, not enough data for preview">
<ShowChart onClick={onClick} className={props.classes.icon} style={{ color: '#aaa' }} />
</Tooltip>
)
return (
<span>
{preview}
<Popper open={open} anchorEl={chartIconRef.current} placement="left-end">
<Fade in={open} timeout={300}>
<Paper style={{ width: '300px' }}>
{open ? <TopicPlot history={props.treeNode.messageHistory} dotPath={props.literal.path} /> : <span />}
</Paper>
</Fade>
</Popper>
</span>
)
}
const mapDispatchToProps = (dispatch: any) => {
return {
actions: {
chart: bindActionCreators(chartActions, dispatch),
},
}
}
export default connect(
undefined,
mapDispatchToProps
)(ChartPreview)

View File

@@ -2,10 +2,10 @@ import * as diff from 'diff'
import * as q from '../../../../../backend/src/Model'
import * as React from 'react'
import Add from '@material-ui/icons/Add'
import ChartPreview from './ChartPreview'
import Remove from '@material-ui/icons/Remove'
import ShowChart from '@material-ui/icons/ShowChart'
import TopicPlot from '../TopicPlot'
import { Fade, Paper, Popper, Theme, Tooltip } from '@material-ui/core'
import { Theme, Tooltip } from '@material-ui/core'
import { JsonPropertyLocation } from '../../../../../backend/src/JsonAstParser'
import { lineChangeStyle, trimNewlineRight } from './util'
import { withStyles } from '@material-ui/styles'
@@ -15,7 +15,7 @@ interface Props {
literalPositions: Array<JsonPropertyLocation>
classes: any
className: string
messageHistory: q.MessageHistory
treeNode: q.TreeNode<any>
}
const style = (theme: Theme) => {
@@ -29,12 +29,9 @@ const style = (theme: Theme) => {
return {
icon,
iconDisabled: {
...icon,
color: theme.palette.text.disabled,
},
iconButton: {
...icon,
marginTop: '0px',
width: '16px',
height: '16px',
padding: '2px',
@@ -52,66 +49,24 @@ const style = (theme: Theme) => {
}
}
function ChartIcon(props: { messageHistory: q.MessageHistory; classes: any; literal: JsonPropertyLocation }) {
const chartIconRef = React.useRef(null)
const [open, setOpen] = React.useState(false)
const mouseOver = React.useCallback(
(event: React.MouseEvent<Element>) => {
setOpen(true)
},
[props.literal.path]
)
const mouseOut = React.useCallback(() => {
setOpen(false)
}, [])
return (
<span>
<ShowChart ref={chartIconRef} className={props.classes.icon} onMouseEnter={mouseOver} onMouseLeave={mouseOut} />
<Popper open={open} anchorEl={chartIconRef.current} placement="left-end">
<Fade in={open} timeout={300}>
<Paper style={{ width: '300px' }}>
{open ? <TopicPlot history={props.messageHistory} dotPath={props.literal.path} /> : <span />}
</Paper>
</Fade>
</Popper>
</span>
)
}
function tokensForLine(change: diff.Change, line: number, props: Props) {
const { classes, literalPositions } = props
const hasEnoughDataToDisplayDiagrams = props.messageHistory.count() > 1
const literal = literalPositions[line]
let chartIcon = null
let chartPreview = null
if (literal) {
if (hasEnoughDataToDisplayDiagrams) {
chartIcon = (
<ChartIcon
messageHistory={props.messageHistory}
classes={{ icon: props.classes.iconButton }}
literal={literal}
/>
)
} else {
chartIcon = (
<Tooltip title="Not enough data">
<ShowChart className={props.classes.iconDisabled} style={{ color: '#aaa' }} />
</Tooltip>
)
}
chartPreview = (
<ChartPreview treeNode={props.treeNode} classes={{ icon: props.classes.iconButton }} literal={literal} />
)
}
if (change.added) {
return [chartIcon, <Add key="add" className={classes.icon} />]
return [chartPreview, <Add key="add" className={classes.icon} />]
} else if (change.removed) {
return [<Remove key="remove" className={classes.icon} />]
} else {
return [
chartIcon,
chartPreview,
<div
key="placeholder"
style={{ width: '12px', display: 'inline-block' }}

View File

@@ -12,7 +12,7 @@ import { withStyles } from '@material-ui/core'
import 'prismjs/components/prism-json'
interface Props {
messageHistory: q.MessageHistory
treeNode: q.TreeNode<any>
previous: string
current: string
nameOfCompareMessage: string
@@ -93,7 +93,7 @@ class CodeDiff extends React.Component<Props, State> {
<Gutters
className={this.props.classes.gutters}
changes={changes}
messageHistory={this.props.messageHistory}
treeNode={this.props.treeNode}
literalPositions={literalPositions}
/>
<pre className={this.props.classes.codeBlock}>{code}</pre>

View File

@@ -188,7 +188,6 @@ const mapDispatchToProps = (dispatch: any) => {
const styles = (theme: Theme) => ({
drawer: {
display: 'block' as 'block',
height: '100%',
},
badge: {
top: '3px',

View File

@@ -1,45 +0,0 @@
import * as dotProp from 'dot-prop'
import * as q from '../../../../backend/src/Model'
import * as React from 'react'
import PlotHistory from './PlotHistory'
import { Base64Message } from '../../../../backend/src/Model/Base64Message'
import { toPlottableValue } from './CodeDiff/util'
interface Props {
history: q.MessageHistory
dotPath?: string
}
function nodeToHistory(history: q.MessageHistory) {
return history
.toArray()
.map((message: q.Message) => {
const value = message.value ? toPlottableValue(Base64Message.toUnicodeString(message.value)) : NaN
return { x: message.received.getTime(), y: toPlottableValue(value) }
})
.filter(data => !isNaN(data.y as any)) as any
}
function nodeDotPathToHistory(history: q.MessageHistory, dotPath: string) {
return history
.toArray()
.map((message: q.Message) => {
let json = {}
try {
json = message.value ? JSON.parse(Base64Message.toUnicodeString(message.value)) : {}
} catch (ignore) {}
let value = dotProp.get(json, dotPath)
return { x: message.received.getTime(), y: toPlottableValue(value) }
})
.filter(data => !isNaN(data.y as any)) as any
}
function render(props: Props) {
const data = props.dotPath ? nodeDotPathToHistory(props.history, props.dotPath) : nodeToHistory(props.history)
console.log(props.dotPath, data)
return <PlotHistory data={data} />
}
export default render

View File

@@ -4,7 +4,7 @@ import BarChart from '@material-ui/icons/BarChart'
import Copy from '../../helper/Copy'
import DateFormatter from '../../helper/DateFormatter'
import History from '../HistoryDrawer'
import TopicPlot from '../TopicPlot'
import TopicPlot from '../../TopicPlot'
import { Base64Message } from '../../../../../backend/src/Model/Base64Message'
import { isPlottable } from '../CodeDiff/util'
import { TopicViewModel } from '../../../model/TopicViewModel'

View File

@@ -53,13 +53,7 @@ class ValuePanel extends React.Component<Props, State> {
return null
}
return (
<ValueRenderer
message={node.message}
messageHistory={node.messageHistory}
compareWith={this.props.compareMessage}
/>
)
return <ValueRenderer treeNode={node} message={node.message} compareWith={this.props.compareMessage} />
}
private renderViewOptions() {

View File

@@ -9,7 +9,7 @@ import { ValueRendererDisplayMode } from '../../../reducers/Settings'
interface Props {
message: q.Message
messageHistory: q.MessageHistory
treeNode: q.TreeNode<any>
compareWith?: q.Message
renderMode: ValueRendererDisplayMode
}
@@ -27,7 +27,7 @@ class ValueRenderer extends React.Component<Props, State> {
private renderDiff(current: string = '', previous: string = '', language?: 'json') {
return (
<CodeDiff
messageHistory={this.props.messageHistory}
treeNode={this.props.treeNode}
previous={previous}
current={current}
language={language}
@@ -65,9 +65,8 @@ class ValueRenderer extends React.Component<Props, State> {
}
public renderValue() {
const { message, messageHistory, compareWith, renderMode } = this.props
const previousMessages = messageHistory.toArray()
const { message, treeNode, compareWith, renderMode } = this.props
const previousMessages = treeNode.messageHistory.toArray()
const previousMessage = previousMessages[previousMessages.length - 2]
let compareMessage = compareWith || previousMessage || message
if (renderMode === 'raw') {