Add numeric chart panel
This commit is contained in:
82
app/src/components/Sidebar/CodeDiff/ChartPreview.tsx
Normal file
82
app/src/components/Sidebar/CodeDiff/ChartPreview.tsx
Normal 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)
|
||||
@@ -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' }}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -188,7 +188,6 @@ const mapDispatchToProps = (dispatch: any) => {
|
||||
const styles = (theme: Theme) => ({
|
||||
drawer: {
|
||||
display: 'block' as 'block',
|
||||
height: '100%',
|
||||
},
|
||||
badge: {
|
||||
top: '3px',
|
||||
|
||||
@@ -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
|
||||
@@ -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'
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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') {
|
||||
|
||||
Reference in New Issue
Block a user