Merge pull request #2940 from influxdata/feature/table-graph-cell-hover
TableGraph- hover time and crosshairspull/10616/head
commit
2d37fe09ba
|
@ -16,7 +16,6 @@ const Dashboard = ({
|
|||
autoRefresh,
|
||||
manualRefresh,
|
||||
onDeleteCell,
|
||||
synchronizer,
|
||||
onPositionChange,
|
||||
inPresentationMode,
|
||||
onOpenTemplateManager,
|
||||
|
@ -26,6 +25,8 @@ const Dashboard = ({
|
|||
showTemplateControlBar,
|
||||
setScrollTop,
|
||||
inView,
|
||||
onSetHoverTime,
|
||||
hoverTime,
|
||||
}) => {
|
||||
const cells = dashboard.cells.map(cell => {
|
||||
const dashboardCell = {
|
||||
|
@ -66,7 +67,8 @@ const Dashboard = ({
|
|||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
manualRefresh={manualRefresh}
|
||||
synchronizer={synchronizer}
|
||||
hoverTime={hoverTime}
|
||||
onSetHoverTime={onSetHoverTime}
|
||||
onDeleteCell={onDeleteCell}
|
||||
onPositionChange={onPositionChange}
|
||||
templates={templatesIncludingDashTime}
|
||||
|
@ -112,7 +114,8 @@ Dashboard.propTypes = {
|
|||
onPositionChange: func,
|
||||
onDeleteCell: func,
|
||||
onSummonOverlayTechnologies: func,
|
||||
synchronizer: func,
|
||||
hoverTime: string,
|
||||
onSetHoverTime: func,
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string,
|
||||
|
|
|
@ -5,7 +5,6 @@ import {withRouter} from 'react-router'
|
|||
import {bindActionCreators} from 'redux'
|
||||
|
||||
import _ from 'lodash'
|
||||
import Dygraph from 'src/external/dygraph'
|
||||
|
||||
import {isUserAuthorized, EDITOR_ROLE} from 'src/auth/Authorized'
|
||||
|
||||
|
@ -19,6 +18,7 @@ import ManualRefresh from 'src/shared/components/ManualRefresh'
|
|||
import {errorThrown as errorThrownAction} from 'shared/actions/errors'
|
||||
import {publishNotification} from 'shared/actions/notifications'
|
||||
import idNormalizer, {TYPE_ID} from 'src/normalizers/id'
|
||||
import {NULL_HOVER_TIME} from 'src/shared/constants/tableGraph'
|
||||
|
||||
import * as dashboardActionCreators from 'src/dashboards/actions'
|
||||
import * as annotationActions from 'shared/actions/annotations'
|
||||
|
@ -54,6 +54,7 @@ class DashboardPage extends Component {
|
|||
zoomedTimeRange: {zoomedLower: null, zoomedUpper: null},
|
||||
scrollTop: 0,
|
||||
windowHeight: window.innerHeight,
|
||||
hoverTime: NULL_HOVER_TIME,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,30 +239,8 @@ class DashboardPage extends Component {
|
|||
this.props.errorThrown(error)
|
||||
}
|
||||
|
||||
synchronizer = dygraph => {
|
||||
const dygraphs = [...this.dygraphs, dygraph].filter(d => d.graphDiv)
|
||||
const {dashboards, params: {dashboardID}} = this.props
|
||||
|
||||
const dashboard = dashboards.find(
|
||||
d => d.id === idNormalizer(TYPE_ID, dashboardID)
|
||||
)
|
||||
|
||||
// Get only the graphs that can sync the hover line
|
||||
const graphsToSync = dashboard.cells.filter(c => c.type !== 'single-stat')
|
||||
|
||||
if (
|
||||
dashboard &&
|
||||
dygraphs.length === graphsToSync.length &&
|
||||
dygraphs.length > 1
|
||||
) {
|
||||
Dygraph.synchronize(dygraphs, {
|
||||
selection: true,
|
||||
zoom: false,
|
||||
range: false,
|
||||
})
|
||||
}
|
||||
|
||||
this.dygraphs = dygraphs
|
||||
handleSetHoverTime = hoverTime => {
|
||||
this.setState({hoverTime})
|
||||
}
|
||||
|
||||
handleToggleTempVarControls = () => {
|
||||
|
@ -277,9 +256,8 @@ class DashboardPage extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {zoomedTimeRange} = this.state
|
||||
const {zoomedTimeRange, hoverTime} = this.state
|
||||
const {zoomedLower, zoomedUpper} = zoomedTimeRange
|
||||
|
||||
const {
|
||||
source,
|
||||
sources,
|
||||
|
@ -440,7 +418,8 @@ class DashboardPage extends Component {
|
|||
manualRefresh={manualRefresh}
|
||||
onZoom={this.handleZoomedTimeRange}
|
||||
onAddCell={this.handleAddCell}
|
||||
synchronizer={this.synchronizer}
|
||||
hoverTime={hoverTime}
|
||||
onSetHoverTime={this.handleSetHoverTime}
|
||||
inPresentationMode={inPresentationMode}
|
||||
onPositionChange={this.handleUpdatePosition}
|
||||
onSelectTemplate={this.handleSelectTemplate}
|
||||
|
|
|
@ -526,9 +526,9 @@ export const GRAPH_TYPES = [
|
|||
graphic: GRAPH_SVGS['line-stepplot'],
|
||||
},
|
||||
{
|
||||
type: 'single-stat',
|
||||
menuOption: 'Single Stat',
|
||||
graphic: GRAPH_SVGS['single-stat'],
|
||||
type: 'bar',
|
||||
menuOption: 'Bar Graph',
|
||||
graphic: GRAPH_SVGS.bar,
|
||||
},
|
||||
{
|
||||
type: 'line-plus-single-stat',
|
||||
|
@ -536,9 +536,9 @@ export const GRAPH_TYPES = [
|
|||
graphic: GRAPH_SVGS['line-plus-single-stat'],
|
||||
},
|
||||
{
|
||||
type: 'bar',
|
||||
menuOption: 'Bar Graph',
|
||||
graphic: GRAPH_SVGS.bar,
|
||||
type: 'single-stat',
|
||||
menuOption: 'Single Stat',
|
||||
graphic: GRAPH_SVGS['single-stat'],
|
||||
},
|
||||
{
|
||||
type: 'gauge',
|
||||
|
|
|
@ -5,8 +5,6 @@ import {bindActionCreators} from 'redux'
|
|||
import _ from 'lodash'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import Dygraph from 'src/external/dygraph'
|
||||
|
||||
import LayoutRenderer from 'shared/components/LayoutRenderer'
|
||||
import DashboardHeader from 'src/dashboards/components/DashboardHeader'
|
||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||
|
@ -84,22 +82,6 @@ class HostPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
synchronizer = dygraph => {
|
||||
const dygraphs = [...this.state.dygraphs, dygraph].filter(d => d.graphDiv)
|
||||
const numGraphs = this.state.layouts.reduce((acc, {cells}) => {
|
||||
return acc + cells.length
|
||||
}, 0)
|
||||
|
||||
if (dygraphs.length === numGraphs) {
|
||||
Dygraph.synchronize(dygraphs, {
|
||||
selection: true,
|
||||
zoom: false,
|
||||
range: false,
|
||||
})
|
||||
}
|
||||
this.setState({dygraphs})
|
||||
}
|
||||
|
||||
renderLayouts = layouts => {
|
||||
const {timeRange} = this.state
|
||||
const {source, autoRefresh, manualRefresh} = this.props
|
||||
|
@ -157,7 +139,6 @@ class HostPage extends Component {
|
|||
autoRefresh={autoRefresh}
|
||||
manualRefresh={manualRefresh}
|
||||
host={this.props.params.hostID}
|
||||
synchronizer={this.synchronizer}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import React, {PropTypes, Component} from 'react'
|
||||
import {DYGRAPH_CONTAINER_XLABEL_MARGIN} from 'shared/constants'
|
||||
import {NULL_HOVER_TIME} from 'shared/constants/tableGraph'
|
||||
|
||||
import classnames from 'classnames'
|
||||
|
||||
class Crosshair extends Component {
|
||||
render() {
|
||||
const {dygraph, staticLegendHeight, hoverTime} = this.props
|
||||
const crosshairLeft = Math.round(
|
||||
Math.max(-1000, dygraph.toDomXCoord(hoverTime)) || -1000 + 1
|
||||
)
|
||||
const crosshairHeight = `calc(100% - ${staticLegendHeight +
|
||||
DYGRAPH_CONTAINER_XLABEL_MARGIN}px)`
|
||||
|
||||
const crosshairHidden = hoverTime === NULL_HOVER_TIME
|
||||
|
||||
return (
|
||||
<div className="crosshair-container">
|
||||
<div
|
||||
className={classnames('crosshair', {
|
||||
hidden: crosshairHidden,
|
||||
})}
|
||||
style={{
|
||||
left: crosshairLeft,
|
||||
height: crosshairHeight,
|
||||
zIndex: 1999,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {number, shape, string} = PropTypes
|
||||
|
||||
Crosshair.propTypes = {
|
||||
dygraph: shape({}),
|
||||
staticLegendHeight: number,
|
||||
hoverTime: string,
|
||||
}
|
||||
|
||||
export default Crosshair
|
|
@ -10,12 +10,13 @@ import Dygraphs from 'src/external/dygraph'
|
|||
import DygraphLegend from 'src/shared/components/DygraphLegend'
|
||||
import StaticLegend from 'src/shared/components/StaticLegend'
|
||||
import Annotations from 'src/shared/components/Annotations'
|
||||
import Crosshair from 'src/shared/components/Crosshair'
|
||||
|
||||
import getRange, {getStackedRange} from 'shared/parsing/getRangeForDygraph'
|
||||
import {DISPLAY_OPTIONS} from 'src/dashboards/constants'
|
||||
import {buildDefaultYLabel} from 'shared/presenters'
|
||||
import {numberValueFormatter} from 'src/utils/formatting'
|
||||
|
||||
import {NULL_HOVER_TIME} from 'src/shared/constants/tableGraph'
|
||||
import {
|
||||
OPTIONS,
|
||||
LINE_COLORS,
|
||||
|
@ -31,9 +32,9 @@ class Dygraph extends Component {
|
|||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
isSynced: false,
|
||||
isHidden: true,
|
||||
staticLegendHeight: null,
|
||||
isNotHovering: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +54,6 @@ class Dygraph extends Component {
|
|||
logscale: y.scale === LOG,
|
||||
colors: this.getLineColors(),
|
||||
series: this.hashColorDygraphSeries(),
|
||||
unhighlightCallback: this.unhighlightCallback,
|
||||
plugins: [new Dygraphs.Plugins.Crosshair({direction: 'vertical'})],
|
||||
axes: {
|
||||
y: {
|
||||
|
@ -92,11 +92,6 @@ class Dygraph extends Component {
|
|||
|
||||
const {w} = this.dygraph.getArea()
|
||||
this.props.setResolution(w)
|
||||
|
||||
// Simple opt-out for now, if a graph should not be synced
|
||||
if (this.props.synchronizer) {
|
||||
this.sync()
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -194,6 +189,30 @@ class Dygraph extends Component {
|
|||
onZoom(this.formatTimeRange(lower), this.formatTimeRange(upper))
|
||||
}
|
||||
|
||||
eventToTimestamp = ({pageX: pxBetweenMouseAndPage}) => {
|
||||
const {left: pxBetweenGraphAndPage} = this.graphRef.getBoundingClientRect()
|
||||
const graphXCoordinate = pxBetweenMouseAndPage - pxBetweenGraphAndPage
|
||||
const timestamp = this.dygraph.toDataXCoord(graphXCoordinate)
|
||||
const [xRangeStart] = this.dygraph.xAxisRange()
|
||||
const clamped = Math.max(xRangeStart, timestamp)
|
||||
return `${clamped}`
|
||||
}
|
||||
|
||||
handleMouseMove = e => {
|
||||
if (this.props.onSetHoverTime) {
|
||||
const newTime = this.eventToTimestamp(e)
|
||||
this.props.onSetHoverTime(newTime)
|
||||
}
|
||||
this.setState({isNotHovering: false})
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
if (this.props.onSetHoverTime) {
|
||||
this.props.onSetHoverTime(NULL_HOVER_TIME)
|
||||
}
|
||||
this.setState({isNotHovering: true})
|
||||
}
|
||||
|
||||
hashColorDygraphSeries = () => {
|
||||
const {dygraphSeries} = this.props
|
||||
const colors = this.getLineColors()
|
||||
|
@ -209,13 +228,6 @@ class Dygraph extends Component {
|
|||
return hashColorDygraphSeries
|
||||
}
|
||||
|
||||
sync = () => {
|
||||
if (!this.state.isSynced) {
|
||||
this.props.synchronizer(this.dygraph)
|
||||
this.setState({isSynced: true})
|
||||
}
|
||||
}
|
||||
|
||||
handleHideLegend = e => {
|
||||
const {top, bottom, left, right} = this.graphRef.getBoundingClientRect()
|
||||
|
||||
|
@ -302,8 +314,7 @@ class Dygraph extends Component {
|
|||
|
||||
render() {
|
||||
const {isHidden, staticLegendHeight} = this.state
|
||||
const {staticLegend, children} = this.props
|
||||
|
||||
const {staticLegend, children, hoverTime} = this.props
|
||||
const nestedGraph = (children && children.length && children[0]) || children
|
||||
let dygraphStyle = {...this.props.containerStyle, zIndex: '2'}
|
||||
if (staticLegend) {
|
||||
|
@ -319,18 +330,25 @@ class Dygraph extends Component {
|
|||
return (
|
||||
<div className="dygraph-child" onMouseLeave={this.deselectCrosshair}>
|
||||
{this.dygraph &&
|
||||
<Annotations
|
||||
dygraph={this.dygraph}
|
||||
annotationsRef={this.handleAnnotationsRef}
|
||||
staticLegendHeight={staticLegendHeight}
|
||||
/>}
|
||||
{this.dygraph &&
|
||||
<DygraphLegend
|
||||
isHidden={isHidden}
|
||||
dygraph={this.dygraph}
|
||||
onHide={this.handleHideLegend}
|
||||
onShow={this.handleShowLegend}
|
||||
/>}
|
||||
<div className="dygraph-addons">
|
||||
<Annotations
|
||||
dygraph={this.dygraph}
|
||||
annotationsRef={this.handleAnnotationsRef}
|
||||
staticLegendHeight={staticLegendHeight}
|
||||
/>
|
||||
<DygraphLegend
|
||||
isHidden={isHidden}
|
||||
dygraph={this.dygraph}
|
||||
onHide={this.handleHideLegend}
|
||||
onShow={this.handleShowLegend}
|
||||
/>
|
||||
{this.state.isNotHovering &&
|
||||
<Crosshair
|
||||
dygraph={this.dygraph}
|
||||
staticLegendHeight={staticLegendHeight}
|
||||
hoverTime={hoverTime}
|
||||
/>}
|
||||
</div>}
|
||||
<div
|
||||
ref={r => {
|
||||
this.graphRef = r
|
||||
|
@ -338,6 +356,8 @@ class Dygraph extends Component {
|
|||
}}
|
||||
className="dygraph-child-container"
|
||||
style={dygraphStyle}
|
||||
onMouseMove={this.handleMouseMove}
|
||||
onMouseOut={this.handleMouseOut}
|
||||
/>
|
||||
{staticLegend &&
|
||||
<StaticLegend
|
||||
|
@ -407,7 +427,8 @@ Dygraph.propTypes = {
|
|||
timeRange: shape({
|
||||
lower: string.isRequired,
|
||||
}),
|
||||
synchronizer: func,
|
||||
hoverTime: string,
|
||||
onSetHoverTime: func,
|
||||
setResolution: func,
|
||||
dygraphRef: func,
|
||||
onZoom: func,
|
||||
|
|
|
@ -53,12 +53,13 @@ const Layout = (
|
|||
autoRefresh,
|
||||
manualRefresh,
|
||||
onDeleteCell,
|
||||
synchronizer,
|
||||
resizeCoords,
|
||||
onCancelEditCell,
|
||||
onStopAddAnnotation,
|
||||
onSummonOverlayTechnologies,
|
||||
grabDataForDownload,
|
||||
hoverTime,
|
||||
onSetHoverTime,
|
||||
},
|
||||
{source: defaultSource}
|
||||
) =>
|
||||
|
@ -85,7 +86,8 @@ const Layout = (
|
|||
timeRange={timeRange}
|
||||
templates={templates}
|
||||
autoRefresh={autoRefresh}
|
||||
synchronizer={synchronizer}
|
||||
hoverTime={hoverTime}
|
||||
onSetHoverTime={onSetHoverTime}
|
||||
manualRefresh={manualRefresh}
|
||||
onStopAddAnnotation={onStopAddAnnotation}
|
||||
grabDataForDownload={grabDataForDownload}
|
||||
|
@ -149,7 +151,8 @@ const propTypes = {
|
|||
onEditCell: func,
|
||||
onDeleteCell: func,
|
||||
onSummonOverlayTechnologies: func,
|
||||
synchronizer: func,
|
||||
hoverTime: string,
|
||||
onSetHoverTime: func,
|
||||
isStatusPage: bool,
|
||||
isEditable: bool,
|
||||
onCancelEditCell: func,
|
||||
|
|
|
@ -72,9 +72,10 @@ class LayoutRenderer extends Component {
|
|||
autoRefresh,
|
||||
manualRefresh,
|
||||
onDeleteCell,
|
||||
synchronizer,
|
||||
onCancelEditCell,
|
||||
onSummonOverlayTechnologies,
|
||||
hoverTime,
|
||||
onSetHoverTime,
|
||||
} = this.props
|
||||
|
||||
const {rowHeight, resizeCoords} = this.state
|
||||
|
@ -129,7 +130,8 @@ class LayoutRenderer extends Component {
|
|||
autoRefresh={autoRefresh}
|
||||
resizeCoords={resizeCoords}
|
||||
onDeleteCell={onDeleteCell}
|
||||
synchronizer={synchronizer}
|
||||
hoverTime={hoverTime}
|
||||
onSetHoverTime={onSetHoverTime}
|
||||
manualRefresh={manualRefresh}
|
||||
onCancelEditCell={onCancelEditCell}
|
||||
onStopAddAnnotation={this.handleStopAddAnnotation}
|
||||
|
@ -184,7 +186,8 @@ LayoutRenderer.propTypes = {
|
|||
onEditCell: func,
|
||||
onDeleteCell: func,
|
||||
onSummonOverlayTechnologies: func,
|
||||
synchronizer: func,
|
||||
hoverTime: string,
|
||||
onSetHoverTime: func,
|
||||
isStatusPage: bool,
|
||||
isEditable: bool,
|
||||
onCancelEditCell: func,
|
||||
|
|
|
@ -49,7 +49,6 @@ class LineGraph extends Component {
|
|||
ruleValues,
|
||||
isBarGraph,
|
||||
resizeCoords,
|
||||
synchronizer,
|
||||
isRefreshing,
|
||||
setResolution,
|
||||
isGraphFilled,
|
||||
|
@ -59,6 +58,8 @@ class LineGraph extends Component {
|
|||
underlayCallback,
|
||||
overrideLineColors,
|
||||
isFetchingInitially,
|
||||
hoverTime,
|
||||
onSetHoverTime,
|
||||
} = this.props
|
||||
|
||||
const {labels, timeSeries, dygraphSeries} = this._timeSeries
|
||||
|
@ -111,7 +112,8 @@ class LineGraph extends Component {
|
|||
isBarGraph={isBarGraph}
|
||||
timeSeries={timeSeries}
|
||||
ruleValues={ruleValues}
|
||||
synchronizer={synchronizer}
|
||||
hoverTime={hoverTime}
|
||||
onSetHoverTime={onSetHoverTime}
|
||||
resizeCoords={resizeCoords}
|
||||
dygraphSeries={dygraphSeries}
|
||||
setResolution={setResolution}
|
||||
|
@ -186,7 +188,8 @@ LineGraph.propTypes = {
|
|||
lower: string.isRequired,
|
||||
}),
|
||||
isInDataExplorer: bool,
|
||||
synchronizer: func,
|
||||
hoverTime: string,
|
||||
onSetHoverTime: func,
|
||||
setResolution: func,
|
||||
cellHeight: number,
|
||||
cell: shape(),
|
||||
|
|
|
@ -29,10 +29,11 @@ const RefreshingGraph = ({
|
|||
resizerTopHeight,
|
||||
staticLegend,
|
||||
manualRefresh, // when changed, re-mounts the component
|
||||
synchronizer,
|
||||
resizeCoords,
|
||||
editQueryStatus,
|
||||
grabDataForDownload,
|
||||
hoverTime,
|
||||
onSetHoverTime,
|
||||
}) => {
|
||||
const prefix = (axes && axes.y.prefix) || ''
|
||||
const suffix = (axes && axes.y.suffix) || ''
|
||||
|
@ -94,8 +95,8 @@ const RefreshingGraph = ({
|
|||
resizerTopHeight={resizerTopHeight}
|
||||
resizeCoords={resizeCoords}
|
||||
cellID={cellID}
|
||||
// prefix={prefix}
|
||||
// suffix={suffix}
|
||||
hoverTime={hoverTime}
|
||||
onSetHoverTime={onSetHoverTime}
|
||||
inView={inView}
|
||||
/>
|
||||
)
|
||||
|
@ -118,7 +119,8 @@ const RefreshingGraph = ({
|
|||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
isBarGraph={type === 'bar'}
|
||||
synchronizer={synchronizer}
|
||||
hoverTime={hoverTime}
|
||||
onSetHoverTime={onSetHoverTime}
|
||||
resizeCoords={resizeCoords}
|
||||
staticLegend={staticLegend}
|
||||
displayOptions={displayOptions}
|
||||
|
@ -138,7 +140,8 @@ RefreshingGraph.propTypes = {
|
|||
autoRefresh: number.isRequired,
|
||||
manualRefresh: number,
|
||||
templates: arrayOf(shape()),
|
||||
synchronizer: func,
|
||||
hoverTime: string,
|
||||
onSetHoverTime: func,
|
||||
type: string.isRequired,
|
||||
cellHeight: number,
|
||||
resizerTopHeight: number,
|
||||
|
|
|
@ -2,32 +2,72 @@ import React, {Component} from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
import _ from 'lodash'
|
||||
import classnames from 'classnames'
|
||||
import {timeSeriesToTable} from 'src/utils/timeSeriesToDygraph'
|
||||
|
||||
import {timeSeriesToTableGraph} from 'src/utils/timeSeriesToDygraph'
|
||||
import {
|
||||
NULL_COLUMN_INDEX,
|
||||
NULL_ROW_INDEX,
|
||||
NULL_HOVER_TIME,
|
||||
} from 'src/shared/constants/tableGraph'
|
||||
|
||||
import {MultiGrid} from 'react-virtualized'
|
||||
|
||||
class TableGraph extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
hoveredColumnIndex: NULL_COLUMN_INDEX,
|
||||
hoveredRowIndex: NULL_ROW_INDEX,
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._labels = []
|
||||
this._data = [[]]
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps) {
|
||||
// TODO: determine if in dataExplorer
|
||||
const {labels, data} = timeSeriesToTable(nextProps.data)
|
||||
const {labels, data} = timeSeriesToTableGraph(nextProps.data)
|
||||
this._labels = labels
|
||||
this._data = data
|
||||
}
|
||||
|
||||
cellRenderer = ({columnIndex, key, rowIndex, style}) => {
|
||||
handleHover = (columnIndex, rowIndex) => () => {
|
||||
if (this.props.onSetHoverTime) {
|
||||
this.props.onSetHoverTime(this._data[rowIndex][0].toString())
|
||||
this.setState({
|
||||
hoveredColumnIndex: columnIndex,
|
||||
hoveredRowIndex: rowIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
if (this.props.onSetHoverTime) {
|
||||
this.props.onSetHoverTime(NULL_HOVER_TIME)
|
||||
this.setState({
|
||||
hoveredColumnIndex: NULL_COLUMN_INDEX,
|
||||
hoveredRowIndex: NULL_ROW_INDEX,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
cellRenderer = ({columnIndex, rowIndex, key, style, parent}) => {
|
||||
const data = this._data
|
||||
const {hoveredColumnIndex, hoveredRowIndex} = this.state
|
||||
|
||||
const columnCount = _.get(data, ['0', 'length'], 0)
|
||||
const rowCount = data.length
|
||||
|
||||
const isFixedRow = rowIndex === 0 && columnIndex > 0
|
||||
const isFixedColumn = rowIndex > 0 && columnIndex === 0
|
||||
const isFixedCorner = rowIndex === 0 && columnIndex === 0
|
||||
const isLastRow = rowIndex === rowCount - 1
|
||||
const isLastColumn = columnIndex === columnCount - 1
|
||||
const isHighlighted =
|
||||
rowIndex === parent.props.scrollToRow ||
|
||||
(rowIndex === hoveredRowIndex && hoveredRowIndex !== 0) ||
|
||||
(columnIndex === hoveredColumnIndex && hoveredColumnIndex !== 0)
|
||||
const dataIsNumerical = _.isNumber([rowIndex][columnIndex])
|
||||
|
||||
const cellClass = classnames('table-graph-cell', {
|
||||
'table-graph-cell__fixed-row': isFixedRow,
|
||||
|
@ -35,16 +75,25 @@ class TableGraph extends Component {
|
|||
'table-graph-cell__fixed-corner': isFixedCorner,
|
||||
'table-graph-cell__last-row': isLastRow,
|
||||
'table-graph-cell__last-column': isLastColumn,
|
||||
'table-graph-cell__highlight': isHighlighted,
|
||||
'table-graph-cell__numerical': dataIsNumerical,
|
||||
})
|
||||
|
||||
return (
|
||||
<div key={key} className={cellClass} style={style}>
|
||||
{data[rowIndex][columnIndex]}
|
||||
<div
|
||||
key={key}
|
||||
style={style}
|
||||
className={cellClass}
|
||||
onMouseOver={this.handleHover(columnIndex, rowIndex)}
|
||||
>
|
||||
{`${data[rowIndex][columnIndex]}`}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {hoveredColumnIndex, hoveredRowIndex} = this.state
|
||||
const {hoverTime} = this.props
|
||||
const data = this._data
|
||||
const columnCount = _.get(data, ['0', 'length'], 0)
|
||||
const rowCount = data.length
|
||||
|
@ -53,35 +102,49 @@ class TableGraph extends Component {
|
|||
const tableWidth = this.gridContainer ? this.gridContainer.clientWidth : 0
|
||||
const tableHeight = this.gridContainer ? this.gridContainer.clientHeight : 0
|
||||
|
||||
const hoverTimeRow =
|
||||
data.length > 1 && hoverTime > 0
|
||||
? data.findIndex(
|
||||
row => row[0] && _.isNumber(row[0]) && row[0] >= hoverTime
|
||||
)
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<div
|
||||
className="table-graph-container"
|
||||
ref={gridContainer => (this.gridContainer = gridContainer)}
|
||||
onMouseOut={this.handleMouseOut}
|
||||
>
|
||||
{data.length > 1 &&
|
||||
<MultiGrid
|
||||
fixedColumnCount={1}
|
||||
fixedRowCount={1}
|
||||
cellRenderer={this.cellRenderer}
|
||||
columnCount={columnCount}
|
||||
columnWidth={COLUMN_WIDTH}
|
||||
height={tableHeight}
|
||||
rowCount={rowCount}
|
||||
rowHeight={ROW_HEIGHT}
|
||||
height={tableHeight}
|
||||
width={tableWidth}
|
||||
fixedColumnCount={1}
|
||||
fixedRowCount={1}
|
||||
enableFixedColumnScroll={true}
|
||||
enableFixedRowScroll={true}
|
||||
scrollToRow={hoverTimeRow}
|
||||
cellRenderer={this.cellRenderer}
|
||||
hoveredColumnIndex={hoveredColumnIndex}
|
||||
hoveredRowIndex={hoveredRowIndex}
|
||||
hoverTime={hoverTime}
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {arrayOf, number, shape} = PropTypes
|
||||
const {arrayOf, number, shape, string, func} = PropTypes
|
||||
|
||||
TableGraph.propTypes = {
|
||||
cellHeight: number,
|
||||
data: arrayOf(shape()),
|
||||
hoverTime: string,
|
||||
onSetHoverTime: func,
|
||||
}
|
||||
|
||||
export default TableGraph
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export const NULL_COLUMN_INDEX = -1
|
||||
export const NULL_ROW_INDEX = -1
|
||||
|
||||
export const NULL_HOVER_TIME = '0'
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
// Components
|
||||
@import 'components/annotations';
|
||||
@import 'components/crosshairs';
|
||||
@import 'components/ceo-display-options';
|
||||
@import 'components/confirm-button';
|
||||
@import 'components/confirm-buttons';
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
Crosshairs
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
%crosshair-styles {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.crosshair-container {
|
||||
@extend %crosshair-styles;
|
||||
z-index: 0;
|
||||
top: 8px;
|
||||
width: calc(100% - 32px);
|
||||
height: calc(100% - 16px);
|
||||
}
|
||||
|
||||
.crosshair {
|
||||
@extend %crosshair-styles;
|
||||
top: 0;
|
||||
height: calc(100% - 20px);
|
||||
width: 0.5px;
|
||||
background-color: $g14-chromium;
|
||||
}
|
||||
|
||||
.crosshair.hidden {
|
||||
visibility: hidden;
|
||||
}
|
|
@ -4,45 +4,104 @@
|
|||
*/
|
||||
|
||||
.table-graph-container {
|
||||
position: absolute;
|
||||
width: calc(100% - 32px);
|
||||
height: calc(100% - 16px);
|
||||
top: 8px;
|
||||
left: 16px;
|
||||
border: 2px solid $g5-pepper;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
width: calc(100% - 32px);
|
||||
height: calc(100% - 16px);
|
||||
top: 8px;
|
||||
left: 16px;
|
||||
border: 2px solid $g5-pepper;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-graph-cell {
|
||||
line-height: 30px;
|
||||
padding: 0 6px;
|
||||
font-size: 13px;
|
||||
color: $g13-mist;
|
||||
border: 1px solid $g5-pepper;
|
||||
line-height: 28px; // Cell height - 2x border width
|
||||
padding: 0 6px;
|
||||
font-size: 13px;
|
||||
color: $g12-forge;
|
||||
border: 1px solid $g5-pepper;
|
||||
|
||||
&__fixed-row,
|
||||
&__fixed-column {
|
||||
font-weight: 700;
|
||||
color: $g16-pearl;
|
||||
background-color: $g4-onyx;
|
||||
}
|
||||
&__fixed-row {
|
||||
border-top: 0;
|
||||
}
|
||||
&__fixed-column {
|
||||
border-left: 0;
|
||||
}
|
||||
&__fixed-corner {
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
color: $g18-cloud;
|
||||
background-color: $g5-pepper;
|
||||
}
|
||||
&__last-row {
|
||||
border-bottom: 0;
|
||||
}
|
||||
&__last-column {
|
||||
border-right: 0;
|
||||
}
|
||||
// Blue Highlight
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
width: calc(100% + 2px);
|
||||
height: calc(100% + 2px);
|
||||
z-index: 5;
|
||||
border: 2px solid $c-pool;
|
||||
border-radius: 3px;
|
||||
visibility: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&__numerical {
|
||||
font-family: $code-font;
|
||||
}
|
||||
&__fixed-row,
|
||||
&__fixed-column {
|
||||
font-weight: 700;
|
||||
color: $g14-chromium;
|
||||
background-color: $g4-onyx;
|
||||
}
|
||||
&__fixed-row {
|
||||
border-top: 0;
|
||||
}
|
||||
&__fixed-column {
|
||||
border-left: 0;
|
||||
}
|
||||
&__fixed-corner {
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
color: $g18-cloud;
|
||||
background-color: $g5-pepper;
|
||||
}
|
||||
&__last-row {
|
||||
border-bottom: 0;
|
||||
}
|
||||
&__last-column {
|
||||
border-right: 0;
|
||||
}
|
||||
&__highlight {
|
||||
background-color: $g6-smoke;
|
||||
color: $g16-pearl;
|
||||
}
|
||||
&__highlight:not(.table-graph-cell__fixed-row):not(.table-graph-cell__fixed-column):not(.table-graph-cell__fixed-corner) {
|
||||
color: $g14-chromium;
|
||||
background-color: $g6-smoke;
|
||||
}
|
||||
&:hover:not(.table-graph-cell__fixed-row):not(.table-graph-cell__fixed-column):not(.table-graph-cell__fixed-corner) {
|
||||
color: $g20-white;
|
||||
background-color: $g0-obsidian;
|
||||
&:after {visibility: visible;}
|
||||
}
|
||||
}
|
||||
|
||||
.ReactVirtualized__Grid {
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
|
||||
&-button {
|
||||
background-color: #f00;
|
||||
}
|
||||
&-track {
|
||||
background-color: #f00;
|
||||
}
|
||||
&-track-piece {
|
||||
background-color: #f00;
|
||||
}
|
||||
&-thumb {
|
||||
background-color: #fff;
|
||||
}
|
||||
&-corner {
|
||||
background-color: #f00;
|
||||
}
|
||||
}
|
||||
&::-webkit-resizer {
|
||||
background-color: #f00;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ const cells = {
|
|||
responseIndex: new Array(DEFAULT_SIZE),
|
||||
}
|
||||
|
||||
export default function timeSeriesToDygraph(raw = [], isInDataExplorer) {
|
||||
const timeSeriesTransform = (raw = []) => {
|
||||
// collect results from each influx response
|
||||
const results = reduce(
|
||||
raw,
|
||||
|
@ -139,6 +139,15 @@ export default function timeSeriesToDygraph(raw = [], isInDataExplorer) {
|
|||
}
|
||||
const sortedTimeSeries = _.sortBy(timeSeries, 'time')
|
||||
|
||||
return {
|
||||
sortedLabels,
|
||||
sortedTimeSeries,
|
||||
}
|
||||
}
|
||||
|
||||
export const timeSeriesToDygraph = (raw = [], isInDataExplorer) => {
|
||||
const {sortedLabels, sortedTimeSeries} = timeSeriesTransform(raw)
|
||||
|
||||
const dygraphSeries = reduce(
|
||||
sortedLabels,
|
||||
(acc, {label, responseIndex}) => {
|
||||
|
@ -147,7 +156,6 @@ export default function timeSeriesToDygraph(raw = [], isInDataExplorer) {
|
|||
axis: responseIndex === 0 ? 'y' : 'y2',
|
||||
}
|
||||
}
|
||||
|
||||
return acc
|
||||
},
|
||||
{}
|
||||
|
@ -163,10 +171,17 @@ export default function timeSeriesToDygraph(raw = [], isInDataExplorer) {
|
|||
}
|
||||
}
|
||||
|
||||
export const timeSeriesToTable = data => {
|
||||
const {labels, timeSeries} = timeSeriesToDygraph(data, false)
|
||||
const tableData = timeSeries.length
|
||||
? timeSeries.map(row => row.map(cell => (cell ? cell.toString() : 'null')))
|
||||
: [[]]
|
||||
return {labels, data: [labels, ...tableData]}
|
||||
export const timeSeriesToTableGraph = raw => {
|
||||
const {sortedLabels, sortedTimeSeries} = timeSeriesTransform(raw)
|
||||
|
||||
const labels = ['time', ...map(sortedLabels, ({label}) => label)]
|
||||
|
||||
const tableData = map(sortedTimeSeries, ({time, values}) => [time, ...values])
|
||||
|
||||
return {
|
||||
labels,
|
||||
data: tableData.length ? [labels, ...tableData] : [[]],
|
||||
}
|
||||
}
|
||||
|
||||
export default timeSeriesToDygraph
|
||||
|
|
Loading…
Reference in New Issue