diff --git a/ui/src/shared/components/Crosshair.js b/ui/src/shared/components/Crosshair.js new file mode 100644 index 0000000000..892d87092c --- /dev/null +++ b/ui/src/shared/components/Crosshair.js @@ -0,0 +1,41 @@ +import React, {PropTypes, Component} from 'react' +import {DYGRAPH_CONTAINER_XLABEL_MARGIN} from 'shared/constants' + +class Crosshair extends Component { + render() { + const { + dygraph, + staticLegendHeight, + hoverTime, + handleCrosshairRef, + } = this.props + const crosshairleft = Math.round( + Math.max(-1000, dygraph.toDomXCoord(hoverTime)) || -1000 + 1 + ) + const crosshairHeight = `calc(100% - ${staticLegendHeight + + DYGRAPH_CONTAINER_XLABEL_MARGIN}px)` + return ( +
handleCrosshairRef(el)}> +
+
+ ) + } +} + +const {func, number, shape, string} = PropTypes + +Crosshair.propTypes = { + dygraph: shape({}), + staticLegendHeight: number, + hoverTime: string, + handleCrosshairRef: func, +} + +export default Crosshair diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index f3d8900613..ca8f861b14 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -9,6 +9,7 @@ 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' @@ -196,10 +197,26 @@ class Dygraph extends Component { onZoom(this.formatTimeRange(lower), this.formatTimeRange(upper)) } - highlightCallback = (e, x) => { + clampWithinGraphTimerange = timestamp => { + const [xRangeStart] = this.dygraph.xAxisRange() + return Math.max(xRangeStart, timestamp) + } + + eventToTimestamp = ({pageX: pxBetweenMouseAndPage}) => { + const { + left: pxBetweenGraphAndPage, + } = this.crosshairRef.getBoundingClientRect() + const graphXCoordinate = pxBetweenMouseAndPage - pxBetweenGraphAndPage + const timestamp = this.dygraph.toDataXCoord(graphXCoordinate) + const clamped = this.clampWithinGraphTimerange(timestamp) + return `${clamped}` + } + + highlightCallback = e => { const {onSetHoverTime} = this.props + const newTime = this.eventToTimestamp(e) if (onSetHoverTime) { - onSetHoverTime(x.toString()) + onSetHoverTime(newTime) } } @@ -311,6 +328,7 @@ class Dygraph extends Component { } handleAnnotationsRef = ref => (this.annotationsRef = ref) + handleCrosshairRef = ref => (this.crosshairRef = ref) handleReceiveStaticLegendHeight = staticLegendHeight => { this.setState({staticLegendHeight}) @@ -318,8 +336,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) { @@ -331,22 +348,28 @@ class Dygraph extends Component { height: `calc(100% - ${staticLegendHeight + cellVerticalPadding}px)`, } } - return (
{this.dygraph && - } - {this.dygraph && - } +
+ + + +
}
{ this.graphRef = r diff --git a/ui/src/style/chronograf.scss b/ui/src/style/chronograf.scss index 9c7c0d73b0..562d273a40 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..c5eb1837ec --- /dev/null +++ b/ui/src/style/components/crosshairs.scss @@ -0,0 +1,107 @@ +$crosshair-color: $g20-white; +$crosshair-color__drag: $c-hydrogen; + +$window0: rgba($crosshair-color, 0); +$window15: rgba($crosshair-color, 0.15); +$window35: rgba($crosshair-color, 0.35); + +$active-window0: rgba($crosshair-color__drag, 0); +$active-window15: rgba($crosshair-color__drag, 0.15); +$active-window35: rgba($crosshair-color__drag, 0.35); + +$timestamp-font-size: 14px; +$timestamp-font-weight: 600; + +.crosshair { + position: absolute; + top: 8px; + z-index: 3; + background-color: $crosshair-color; + height: calc(100% - 36px); + width: 2px; + transform: translateX( + -1px + ); // translate should always be half with width to horizontally center the crosshair pos + transition: background-color 0.25s ease; + visibility: visible; + + &.dragging { + background-color: $crosshair-color__drag; + z-index: 4; + } +} + +.crosshair-window { + position: absolute; + top: 8px; + background: linear-gradient(to bottom, $window15 0%, $window0 100%); + border-top: 2px dotted $window35; + z-index: 1; + + &.active { + background: linear-gradient( + to bottom, + $active-window15 0%, + $active-window0 100% + ); + border-top: 2px dotted $active-window35; + } +} + +/* + New crosshairs + ------------------------------------------------------------------------------ +*/ +.new-crosshair { + position: absolute; + z-index: 0; + top: 8px; + width: calc(100% - 32px); + height: calc(100% - 16px); + cursor: pointer; + opacity: 1; +} + +.new-crosshair--crosshair { + opacity: 1; + position: absolute; + top: 0; + height: calc(100% - 20px); + width: 0.5px; + transform: translateX(-1px); + background: linear-gradient(to bottom, $c-dreamsicle 0%, $c-dreamsicle 100%); + transition: opacity 0.4s ease; + z-index: 5; + cursor: pointer; +} + +.new-crosshair-tooltip { + display: flex; + flex-direction: column; + align-items: center; + background: linear-gradient(to bottom, $c-pool 0%, $c-ocean 100%); + border-radius: 4px; + padding: 6px 12px; + position: absolute; + bottom: calc(100% + 8px); + left: 50%; + transform: translateX(-50%); + z-index: 10; +} + +.new-crosshair-helper { + white-space: nowrap; + font-size: 13px; + line-height: 13px; + font-weight: 600; + color: $c-neutrino; + margin-bottom: 4px; +} + +.new-crosshair-timestamp { + white-space: nowrap; + font-size: $timestamp-font-size; + line-height: $timestamp-font-size; + font-weight: $timestamp-font-weight; + color: $g20-white; +}