Refactor chart
This commit is contained in:
105
app/src/components/Chart/Chart.tsx
Normal file
105
app/src/components/Chart/Chart.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import DateFormatter from '../helper/DateFormatter'
|
||||||
|
import NoData from './NoData'
|
||||||
|
import NumberFormatter from '../helper/NumberFormatter'
|
||||||
|
import React, { memo, useCallback } from 'react'
|
||||||
|
import TooltipComponent from './TooltipComponent'
|
||||||
|
import { default as ReactResizeDetector } from 'react-resize-detector'
|
||||||
|
import { emphasize } from '@material-ui/core/styles'
|
||||||
|
import { mapCurveType } from './mapCurveType'
|
||||||
|
import { PlotCurveTypes } from '../../reducers/Charts'
|
||||||
|
import { Point, Tooltip } from './Model'
|
||||||
|
import { Theme, withTheme } from '@material-ui/core'
|
||||||
|
import { useCustomXDomain } from './effects/useCustomXDomain'
|
||||||
|
import { useCustomYDomain } from './effects/useCustomYDomain'
|
||||||
|
import 'react-vis/dist/style.css'
|
||||||
|
const { XYPlot, LineMarkSeries, YAxis, HorizontalGridLines, Hint } = require('react-vis')
|
||||||
|
const abbreviate = require('number-abbreviate')
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
data: Array<{ x: number; y: number }>
|
||||||
|
theme: Theme
|
||||||
|
interpolation?: PlotCurveTypes
|
||||||
|
range?: [number?, number?]
|
||||||
|
timeRangeStart?: number
|
||||||
|
color?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withTheme(
|
||||||
|
memo((props: Props) => {
|
||||||
|
const [width, setWidth] = React.useState(300)
|
||||||
|
const [tooltip, setTooltip] = React.useState<Tooltip | undefined>()
|
||||||
|
const detectResize = React.useCallback(newWidth => setWidth(newWidth), [])
|
||||||
|
|
||||||
|
const hintFormatter = React.useCallback(
|
||||||
|
(point: any) => [
|
||||||
|
{ title: <b>Time</b>, value: <DateFormatter timeFirst={true} date={new Date(point.x)} /> },
|
||||||
|
{ title: <b>Value</b>, value: <NumberFormatter value={point.y} /> },
|
||||||
|
{ title: <b>Raw</b>, value: <span>{point.y}</span> },
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const onMouseLeave = React.useCallback(() => {
|
||||||
|
setTooltip(undefined)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const showTooltip = React.useCallback((point: Point, something: { event: MouseEvent }) => {
|
||||||
|
if (!something) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setTooltip({ point, value: hintFormatter(point), element: something.event.target as any })
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const paletteColor =
|
||||||
|
props.theme.palette.type === 'light' ? props.theme.palette.secondary.dark : props.theme.palette.primary.light
|
||||||
|
const color = props.color ? props.color : paletteColor
|
||||||
|
|
||||||
|
const highlightSelectedPoint = useCallback(
|
||||||
|
(point: Point) => {
|
||||||
|
const highlight = tooltip && tooltip.point.x === point.x && tooltip.point.y === point.y
|
||||||
|
return highlight ? emphasize(color, 0.8) : color
|
||||||
|
},
|
||||||
|
[tooltip, color]
|
||||||
|
)
|
||||||
|
|
||||||
|
const formatYAxis = useCallback((num: number) => abbreviate(num), [])
|
||||||
|
|
||||||
|
const xDomain = useCustomXDomain(props)
|
||||||
|
const yDomain = useCustomYDomain(props)
|
||||||
|
|
||||||
|
const data = props.data
|
||||||
|
const hasData = data.length > 0
|
||||||
|
const dummyDomain = [-1, 1]
|
||||||
|
const dummyData = [{ x: -2, y: -2 }]
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div style={{ height: '150px', width: '100%', position: 'relative' }}>
|
||||||
|
{data.length === 0 ? <NoData /> : null}
|
||||||
|
<XYPlot
|
||||||
|
width={width}
|
||||||
|
height={180}
|
||||||
|
yDomain={hasData ? yDomain : dummyDomain}
|
||||||
|
xDomain={hasData ? xDomain : dummyDomain}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
>
|
||||||
|
<HorizontalGridLines />
|
||||||
|
<YAxis width={45} tickFormat={formatYAxis} />
|
||||||
|
<LineMarkSeries
|
||||||
|
color={color}
|
||||||
|
colorType="literal"
|
||||||
|
getColor={highlightSelectedPoint}
|
||||||
|
onValueMouseOver={showTooltip}
|
||||||
|
size={3}
|
||||||
|
data={hasData ? data : dummyData}
|
||||||
|
curve={mapCurveType(props.interpolation)}
|
||||||
|
/>
|
||||||
|
<Hint value={{ x: 0, y: 0 }} style={{ pointerEvents: 'none' }}>
|
||||||
|
<TooltipComponent tooltip={tooltip} theme={props.theme} />
|
||||||
|
</Hint>
|
||||||
|
</XYPlot>
|
||||||
|
<ReactResizeDetector handleWidth={true} onResize={detectResize} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
15
app/src/components/Chart/Model.ts
Normal file
15
app/src/components/Chart/Model.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export interface Point {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tooltip {
|
||||||
|
value: Array<TooltipRows>
|
||||||
|
point: Point
|
||||||
|
element: Element | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TooltipRows {
|
||||||
|
title: React.ReactElement
|
||||||
|
value: React.ReactElement
|
||||||
|
}
|
||||||
27
app/src/components/Chart/NoData.tsx
Normal file
27
app/src/components/Chart/NoData.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React, { memo } from 'react'
|
||||||
|
import { Typography } from '@material-ui/core'
|
||||||
|
|
||||||
|
function NoData() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: '100%',
|
||||||
|
color: '#ccc',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
paddingLeft: '30px',
|
||||||
|
zIndex: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography style={{ fontWeight: 'bold' }} variant="h5">
|
||||||
|
No Data
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(NoData)
|
||||||
61
app/src/components/Chart/TooltipComponent.tsx
Normal file
61
app/src/components/Chart/TooltipComponent.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import React, { memo } from 'react'
|
||||||
|
import { fade } from '@material-ui/core/styles'
|
||||||
|
import { Fade, Grow, Paper, Popper, Theme, Typography, withTheme } from '@material-ui/core'
|
||||||
|
import { Tooltip } from './Model'
|
||||||
|
const { Hint } = require('react-vis')
|
||||||
|
|
||||||
|
function TooltipComponent(props: { tooltip?: Tooltip; theme: Theme }) {
|
||||||
|
const { tooltip } = props
|
||||||
|
return (
|
||||||
|
<Popper
|
||||||
|
style={Boolean(tooltip) ? { transition: 'all 0.1s ease-out' } : undefined}
|
||||||
|
open={Boolean(tooltip)}
|
||||||
|
transition={true}
|
||||||
|
keepMounted={true}
|
||||||
|
placement="top"
|
||||||
|
anchorEl={tooltip && tooltip.element}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
paddingBottom: '8px',
|
||||||
|
transition: 'all 0.5s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Fade in={Boolean(tooltip)} timeout={300}>
|
||||||
|
<Grow in={Boolean(tooltip)} timeout={300}>
|
||||||
|
<Paper
|
||||||
|
style={{
|
||||||
|
padding: '4px',
|
||||||
|
marginTop: '-12px',
|
||||||
|
backgroundColor: fade(
|
||||||
|
props.theme.palette.type === 'light'
|
||||||
|
? props.theme.palette.background.paper
|
||||||
|
: props.theme.palette.background.default,
|
||||||
|
0.7
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<table style={{ lineHeight: '1.25em' }}>
|
||||||
|
<tbody>
|
||||||
|
{tooltip &&
|
||||||
|
tooltip.value.map((v: any, idx: number) => (
|
||||||
|
<tr key={idx}>
|
||||||
|
<td>
|
||||||
|
<Typography style={{ lineHeight: '1.2' }}>{v.title}</Typography>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Typography style={{ lineHeight: '1.2' }}>{v.value}</Typography>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</Paper>
|
||||||
|
</Grow>
|
||||||
|
</Fade>
|
||||||
|
</div>
|
||||||
|
</Popper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withTheme(memo(TooltipComponent))
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { Props } from './PlotHistory'
|
import { Props } from '../Chart'
|
||||||
|
|
||||||
export function useCustomXDomain(props: Props): [number, number] | undefined {
|
export function useCustomXDomain(props: Props): [number, number] | undefined {
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
39
app/src/components/Chart/effects/useCustomYDomain.tsx
Normal file
39
app/src/components/Chart/effects/useCustomYDomain.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { Props } from '../Chart'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { Point } from '../Model'
|
||||||
|
|
||||||
|
export function useCustomYDomain(props: Props) {
|
||||||
|
return useMemo(() => {
|
||||||
|
const data = props.data
|
||||||
|
const calculatedDomain = domainForData(data)
|
||||||
|
const yDomain: [number, number] = props.range
|
||||||
|
? [props.range[0] || calculatedDomain[0], props.range[1] || calculatedDomain[1]]
|
||||||
|
: calculatedDomain
|
||||||
|
|
||||||
|
return yDomain
|
||||||
|
}, [props.data])
|
||||||
|
}
|
||||||
|
|
||||||
|
function domainForData(data: Array<Point>): [number, number] {
|
||||||
|
if (!data[0]) {
|
||||||
|
const defaultDomain: [number, number] = [-1, 1]
|
||||||
|
return defaultDomain
|
||||||
|
}
|
||||||
|
let max = data[0].y
|
||||||
|
let min = data[0].y
|
||||||
|
data.forEach(d => {
|
||||||
|
if (max < d.y) {
|
||||||
|
max = d.y
|
||||||
|
}
|
||||||
|
if (min > d.y) {
|
||||||
|
min = d.y
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if ((max === 1 || max === 0) && (min === 1 || min === 0)) {
|
||||||
|
return [0, 1]
|
||||||
|
}
|
||||||
|
if (min === max) {
|
||||||
|
return [min - 0.5 * min, min + 0.5 * min]
|
||||||
|
}
|
||||||
|
return [min, max]
|
||||||
|
}
|
||||||
17
app/src/components/Chart/mapCurveType.tsx
Normal file
17
app/src/components/Chart/mapCurveType.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { PlotCurveTypes } from '../../reducers/Charts'
|
||||||
|
export function mapCurveType(type: PlotCurveTypes | undefined) {
|
||||||
|
switch (type) {
|
||||||
|
case 'curve':
|
||||||
|
return 'curveMonotoneX'
|
||||||
|
case 'linear':
|
||||||
|
return 'curveLinear'
|
||||||
|
case 'cubic_basis_spline':
|
||||||
|
return 'curveBasis'
|
||||||
|
case 'step_after':
|
||||||
|
return 'curveStepAfter'
|
||||||
|
case 'step_before':
|
||||||
|
return 'curveStepBefore'
|
||||||
|
default:
|
||||||
|
return 'curveMonotoneX'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as q from '../../../../backend/src/Model'
|
import * as q from '../../../../backend/src/Model'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Chart from './Chart'
|
import TopicChart from './TopicChart'
|
||||||
import { ChartParameters } from '../../reducers/Charts'
|
import { ChartParameters } from '../../reducers/Charts'
|
||||||
import { usePollingToFetchTreeNode } from '../helper/usePollingToFetchTreeNode'
|
import { usePollingToFetchTreeNode } from '../helper/usePollingToFetchTreeNode'
|
||||||
|
|
||||||
@@ -16,5 +16,5 @@ export function ChartWithTreeNode(props: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const treeNode = usePollingToFetchTreeNode(tree, parameters.topic)
|
const treeNode = usePollingToFetchTreeNode(tree, parameters.topic)
|
||||||
return <Chart treeNode={treeNode} parameters={parameters} />
|
return <TopicChart treeNode={treeNode} parameters={parameters} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function useMessageSubscriptionToUpdate(treeNode?: q.TreeNode<any>) {
|
|||||||
return messageHistory
|
return messageHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
function Chart(props: Props) {
|
function TopicChart(props: Props) {
|
||||||
const { parameters, treeNode } = props
|
const { parameters, treeNode } = props
|
||||||
const [frozenHistory, setFrozenHistory] = React.useState<q.MessageHistory | undefined>()
|
const [frozenHistory, setFrozenHistory] = React.useState<q.MessageHistory | undefined>()
|
||||||
const messageHistory = useMessageSubscriptionToUpdate(treeNode)
|
const messageHistory = useMessageSubscriptionToUpdate(treeNode)
|
||||||
@@ -108,4 +108,4 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
export default connect(
|
export default connect(
|
||||||
undefined,
|
undefined,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(Chart)
|
)(TopicChart)
|
||||||
@@ -23,6 +23,8 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class HistoryDrawer extends React.Component<Props, State> {
|
class HistoryDrawer extends React.Component<Props, State> {
|
||||||
|
private handleCtrlA = selectTextWithCtrlA({ targetSelector: 'pre' })
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -40,8 +42,6 @@ class HistoryDrawer extends React.Component<Props, State> {
|
|||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleCtrlA = selectTextWithCtrlA({ targetSelector: 'pre' })
|
|
||||||
|
|
||||||
public renderHistory() {
|
public renderHistory() {
|
||||||
const style = (element: HistoryItem) => ({
|
const style = (element: HistoryItem) => ({
|
||||||
backgroundColor: element.selected
|
backgroundColor: element.selected
|
||||||
|
|||||||
@@ -1,228 +0,0 @@
|
|||||||
import DateFormatter from '../helper/DateFormatter'
|
|
||||||
import NumberFormatter from '../helper/NumberFormatter'
|
|
||||||
import React, { useCallback, useState } from 'react'
|
|
||||||
import { default as ReactResizeDetector } from 'react-resize-detector'
|
|
||||||
import { emphasize, fade } from '@material-ui/core/styles'
|
|
||||||
import { Paper, Popper, Theme, Typography, withTheme } from '@material-ui/core'
|
|
||||||
import { PlotCurveTypes } from '../../reducers/Charts'
|
|
||||||
import { useCustomXDomain } from './useCustomXDomain'
|
|
||||||
import 'react-vis/dist/style.css'
|
|
||||||
const { XYPlot, LineMarkSeries, Hint, YAxis, HorizontalGridLines } = require('react-vis')
|
|
||||||
const abbreviate = require('number-abbreviate')
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
data: Array<{ x: number; y: number }>
|
|
||||||
theme: Theme
|
|
||||||
interpolation?: PlotCurveTypes
|
|
||||||
range?: [number?, number?]
|
|
||||||
timeRangeStart?: number
|
|
||||||
color?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapCurveType(type: PlotCurveTypes | undefined) {
|
|
||||||
switch (type) {
|
|
||||||
case 'curve':
|
|
||||||
return 'curveMonotoneX'
|
|
||||||
case 'linear':
|
|
||||||
return 'curveLinear'
|
|
||||||
case 'cubic_basis_spline':
|
|
||||||
return 'curveBasis'
|
|
||||||
case 'step_after':
|
|
||||||
return 'curveStepAfter'
|
|
||||||
case 'step_before':
|
|
||||||
return 'curveStepBefore'
|
|
||||||
default:
|
|
||||||
return 'curveMonotoneX'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function useToggle(initialState: boolean): [boolean, () => void, (value: boolean) => void] {
|
|
||||||
const [value, setValue] = useState(initialState)
|
|
||||||
const toggle = useCallback(() => {
|
|
||||||
setValue(!value)
|
|
||||||
}, [value])
|
|
||||||
|
|
||||||
return [value, toggle, setValue]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Point {
|
|
||||||
x: any
|
|
||||||
y: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withTheme((props: Props) => {
|
|
||||||
const [hintStaysOpen, toggleHintStaysOpen, setStaysOpen] = useToggle(false)
|
|
||||||
const [width, setWidth] = React.useState(300)
|
|
||||||
const [tooltip, setTooltip] = React.useState<{ value: any; point: Point; element: any } | undefined>()
|
|
||||||
const detectResize = React.useCallback(newWidth => setWidth(newWidth), [])
|
|
||||||
|
|
||||||
const hintFormatter = React.useCallback((point: any) => {
|
|
||||||
return [
|
|
||||||
{ title: <b>Time</b>, value: <DateFormatter timeFirst={true} date={new Date(point.x)} /> },
|
|
||||||
{ title: <b>Value</b>, value: <NumberFormatter value={point.y} /> },
|
|
||||||
{ title: <b>Raw</b>, value: point.y },
|
|
||||||
]
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onMouseLeave = React.useCallback(() => {
|
|
||||||
setStaysOpen(false)
|
|
||||||
setTooltip(undefined)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const showTooltip = React.useCallback((point: Point, something: { event: MouseEvent }) => {
|
|
||||||
if (!something) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setTooltip({ point, value: hintFormatter(point), element: something.event.target })
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const paletteColor =
|
|
||||||
props.theme.palette.type === 'light' ? props.theme.palette.secondary.dark : props.theme.palette.primary.light
|
|
||||||
const color = props.color ? props.color : paletteColor
|
|
||||||
|
|
||||||
const highlightSelectedPoint = useCallback(
|
|
||||||
(point: Point) => {
|
|
||||||
const highlight = tooltip && tooltip.point.x === point.x && tooltip.point.y === point.y
|
|
||||||
return highlight ? emphasize(color, 0.8) : color
|
|
||||||
},
|
|
||||||
[tooltip, color]
|
|
||||||
)
|
|
||||||
|
|
||||||
const getAnchorElement = useCallback(() => tooltip && tooltip.element, [tooltip])
|
|
||||||
const formatYAxis = useCallback((num: number) => abbreviate(num), [])
|
|
||||||
|
|
||||||
const xDomain = useCustomXDomain(props)
|
|
||||||
|
|
||||||
return React.useMemo(() => {
|
|
||||||
const data = props.data
|
|
||||||
const calculatedDomain = domainForData(data)
|
|
||||||
const yDomain: [number, number] = props.range
|
|
||||||
? [props.range[0] || calculatedDomain[0], props.range[1] || calculatedDomain[1]]
|
|
||||||
: calculatedDomain
|
|
||||||
|
|
||||||
const hasData = data.length > 0
|
|
||||||
const dummyDomain = [-1, 1]
|
|
||||||
const dummyData = [{ x: -2, y: -2 }]
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div style={{ height: '150px', width: '100%', position: 'relative' }}>
|
|
||||||
{data.length === 0 ? <NoData /> : null}
|
|
||||||
<XYPlot
|
|
||||||
width={width}
|
|
||||||
height={180}
|
|
||||||
yDomain={hasData ? yDomain : dummyDomain}
|
|
||||||
xDomain={hasData ? xDomain : dummyDomain}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
>
|
|
||||||
<HorizontalGridLines />
|
|
||||||
<YAxis width={45} tickFormat={formatYAxis} />
|
|
||||||
<LineMarkSeries
|
|
||||||
color={color}
|
|
||||||
colorType="literal"
|
|
||||||
getColor={highlightSelectedPoint}
|
|
||||||
onValueMouseOver={showTooltip}
|
|
||||||
size={3}
|
|
||||||
data={hasData ? data : dummyData}
|
|
||||||
curve={mapCurveType(props.interpolation)}
|
|
||||||
/>
|
|
||||||
<Hint
|
|
||||||
value={{}}
|
|
||||||
style={{
|
|
||||||
pointerEvents: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Popper open={Boolean(tooltip)} placement="top" anchorEl={getAnchorElement()}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
paddingBottom: '8px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paper
|
|
||||||
style={{
|
|
||||||
padding: '4px',
|
|
||||||
marginTop: '-12px',
|
|
||||||
backgroundColor: fade(
|
|
||||||
props.theme.palette.type === 'light'
|
|
||||||
? props.theme.palette.background.paper
|
|
||||||
: props.theme.palette.background.default,
|
|
||||||
0.7
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<table style={{ lineHeight: '1.25em' }}>
|
|
||||||
<tbody>
|
|
||||||
{tooltip &&
|
|
||||||
tooltip.value.map((v: any, idx: number) => (
|
|
||||||
<tr key={idx}>
|
|
||||||
<td>
|
|
||||||
<Typography style={{ lineHeight: '1.2' }}>{v.title}</Typography>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Typography style={{ lineHeight: '1.2' }}>{v.value}</Typography>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</Paper>
|
|
||||||
</div>
|
|
||||||
</Popper>
|
|
||||||
</Hint>
|
|
||||||
</XYPlot>
|
|
||||||
<ReactResizeDetector handleWidth={true} onResize={detectResize} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}, [width, props.data, tooltip, props.interpolation, props.range, props.color, props.theme, xDomain])
|
|
||||||
})
|
|
||||||
|
|
||||||
function NoData() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
height: '100%',
|
|
||||||
position: 'absolute',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: '100%',
|
|
||||||
color: '#ccc',
|
|
||||||
verticalAlign: 'middle',
|
|
||||||
paddingLeft: '30px',
|
|
||||||
zIndex: 10,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography style={{ fontWeight: 'bold' }} variant="h5">
|
|
||||||
No Data
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function domainForData(data: Array<{ x: number; y: number }>): [number, number] {
|
|
||||||
if (!data[0]) {
|
|
||||||
const defaultDomain: [number, number] = [-1, 1]
|
|
||||||
return defaultDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
let max = data[0].y
|
|
||||||
let min = data[0].y
|
|
||||||
data.forEach(d => {
|
|
||||||
if (max < d.y) {
|
|
||||||
max = d.y
|
|
||||||
}
|
|
||||||
if (min > d.y) {
|
|
||||||
min = d.y
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if ((max === 1 || max === 0) && (min === 1 || min === 0)) {
|
|
||||||
return [0, 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (min === max) {
|
|
||||||
return [min - 0.5 * min, min + 0.5 * min]
|
|
||||||
}
|
|
||||||
|
|
||||||
return [min, max]
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as dotProp from 'dot-prop'
|
import * as dotProp from 'dot-prop'
|
||||||
import * as q from '../../../backend/src/Model'
|
import * as q from '../../../backend/src/Model'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import PlotHistory from './Sidebar/PlotHistory'
|
import PlotHistory from './Chart/Chart'
|
||||||
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
import { Base64Message } from '../../../backend/src/Model/Base64Message'
|
||||||
import { toPlottableValue } from './Sidebar/CodeDiff/util'
|
import { toPlottableValue } from './Sidebar/CodeDiff/util'
|
||||||
import { PlotCurveTypes } from '../reducers/Charts'
|
import { PlotCurveTypes } from '../reducers/Charts'
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ export const styles = (theme: Theme) => {
|
|||||||
display: 'inline-block' as 'inline-block',
|
display: 'inline-block' as 'inline-block',
|
||||||
whiteSpace: 'nowrap' as 'nowrap',
|
whiteSpace: 'nowrap' as 'nowrap',
|
||||||
height: '14px',
|
height: '14px',
|
||||||
padding: '0 4px',
|
padding: '1px 4px 0 4px',
|
||||||
margin: '1px 0px 2px 0px',
|
margin: '1px 0px',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: theme.palette.type === 'light' ? blueGrey[100] : theme.palette.primary.light,
|
backgroundColor: theme.palette.type === 'light' ? blueGrey[100] : theme.palette.primary.light,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user