diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js index c3428829b7..4aae1b459e 100644 --- a/ui/src/dashboards/components/Dashboard.js +++ b/ui/src/dashboards/components/Dashboard.js @@ -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, diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 1eeec2672f..e60434a0d0 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -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} diff --git a/ui/src/dashboards/graphics/graph.js b/ui/src/dashboards/graphics/graph.js index 9658606c87..4038517bb8 100644 --- a/ui/src/dashboards/graphics/graph.js +++ b/ui/src/dashboards/graphics/graph.js @@ -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', diff --git a/ui/src/hosts/containers/HostPage.js b/ui/src/hosts/containers/HostPage.js index f513305b20..9af0f055ab 100644 --- a/ui/src/hosts/containers/HostPage.js +++ b/ui/src/hosts/containers/HostPage.js @@ -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} /> ) } diff --git a/ui/src/shared/components/Crosshair.js b/ui/src/shared/components/Crosshair.js new file mode 100644 index 0000000000..a111dd35df --- /dev/null +++ b/ui/src/shared/components/Crosshair.js @@ -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 ( +
+
+
+ ) + } +} + +const {number, shape, string} = PropTypes + +Crosshair.propTypes = { + dygraph: shape({}), + staticLegendHeight: number, + hoverTime: string, +} + +export default Crosshair diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index eff78785ba..da9a658986 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -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 (
{this.dygraph && - } - {this.dygraph && - } +
+ + + {this.state.isNotHovering && + } +
}
{ this.graphRef = r @@ -338,6 +356,8 @@ class Dygraph extends Component { }} className="dygraph-child-container" style={dygraphStyle} + onMouseMove={this.handleMouseMove} + onMouseOut={this.handleMouseOut} /> {staticLegend && @@ -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, diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index f853071e41..17d3509e47 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -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, diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 7c73e8ae76..8238da7abd 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -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(), diff --git a/ui/src/shared/components/RefreshingGraph.js b/ui/src/shared/components/RefreshingGraph.js index 7a1544c404..a406327c72 100644 --- a/ui/src/shared/components/RefreshingGraph.js +++ b/ui/src/shared/components/RefreshingGraph.js @@ -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, diff --git a/ui/src/shared/components/TableGraph.js b/ui/src/shared/components/TableGraph.js index 0d9124f276..db970615f3 100644 --- a/ui/src/shared/components/TableGraph.js +++ b/ui/src/shared/components/TableGraph.js @@ -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 ( -
- {data[rowIndex][columnIndex]} +
+ {`${data[rowIndex][columnIndex]}`}
) } 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 (
(this.gridContainer = gridContainer)} + onMouseOut={this.handleMouseOut} > {data.length > 1 && }
) } } -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 diff --git a/ui/src/shared/constants/tableGraph.js b/ui/src/shared/constants/tableGraph.js new file mode 100644 index 0000000000..717d015751 --- /dev/null +++ b/ui/src/shared/constants/tableGraph.js @@ -0,0 +1,4 @@ +export const NULL_COLUMN_INDEX = -1 +export const NULL_ROW_INDEX = -1 + +export const NULL_HOVER_TIME = '0' diff --git a/ui/src/style/chronograf.scss b/ui/src/style/chronograf.scss index 0efcca69bd..ccb2c7f1b4 100644 --- a/ui/src/style/chronograf.scss +++ b/ui/src/style/chronograf.scss @@ -33,6 +33,7 @@ // Components @import 'components/annotations'; +@import 'components/crosshairs'; @import 'components/ceo-display-options'; @import 'components/confirm-button'; @import 'components/confirm-buttons'; diff --git a/ui/src/style/components/crosshairs.scss b/ui/src/style/components/crosshairs.scss new file mode 100644 index 0000000000..a6d488ecca --- /dev/null +++ b/ui/src/style/components/crosshairs.scss @@ -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; +} diff --git a/ui/src/style/components/table-graph.scss b/ui/src/style/components/table-graph.scss index aba63236f7..a909197a12 100644 --- a/ui/src/style/components/table-graph.scss +++ b/ui/src/style/components/table-graph.scss @@ -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; + } } diff --git a/ui/src/utils/timeSeriesToDygraph.js b/ui/src/utils/timeSeriesToDygraph.js index 243a6bd330..902667f45a 100644 --- a/ui/src/utils/timeSeriesToDygraph.js +++ b/ui/src/utils/timeSeriesToDygraph.js @@ -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