Merge pull request #2829 from influxdata/feature/persistent-legend-with-master

Persistent Legend to Annotations
pull/10616/head
Deniz Kusefoglu 2018-02-27 11:57:26 -08:00 committed by GitHub
commit 37fdec9a97
25 changed files with 345 additions and 40 deletions

View File

@ -1,6 +1,7 @@
## v1.4.2.0 [unreleased]
### Features
1. [#2837] (https://github.com/influxdata/chronograf/pull/2837): Prevent execution of queries in cells that are not in view on the dashboard page
1. [#2829] (https://github.com/influxdata/chronograf/pull/2829): Add an optional persistent legend which can toggle series visibility to dashboard cells
### UI Improvements
1. [#2848](https://github.com/influxdata/chronograf/pull/2848): Add ability to set a prefix and suffix on Single Stat and Gauge cell types
1. [#2831](https://github.com/influxdata/chronograf/pull/2831): Rename 'Create Alerts' page to 'Manage Tasks'; Redesign page to improve clarity of purpose

View File

@ -10,11 +10,11 @@ import FancyScrollbar from 'shared/components/FancyScrollbar'
import {DISPLAY_OPTIONS, TOOLTIP_CONTENT} from 'src/dashboards/constants'
import {GRAPH_TYPES} from 'src/dashboards/graphics/graph'
import {updateAxes} from 'src/dashboards/actions/cellEditorOverlay'
const {LINEAR, LOG, BASE_2, BASE_10} = DISPLAY_OPTIONS
const getInputMin = scale => (scale === LOG ? '0' : null)
import {updateAxes} from 'src/dashboards/actions/cellEditorOverlay'
class AxesOptions extends Component {
handleSetPrefixSuffix = e => {
const {handleUpdateAxes, axes} = this.props
@ -73,6 +73,8 @@ class AxesOptions extends Component {
const {
axes: {y: {bounds, label, prefix, suffix, base, scale, defaultYLabel}},
type,
staticLegend,
onToggleStaticLegend,
} = this.props
const [min, max] = bounds
@ -162,6 +164,18 @@ class AxesOptions extends Component {
onClickTab={this.handleSetScale(LOG)}
/>
</Tabber>
<Tabber labelText="Static Legend">
<Tab
text="Show"
isActive={staticLegend}
onClickTab={onToggleStaticLegend(true)}
/>
<Tab
text="Hide"
isActive={!staticLegend}
onClickTab={onToggleStaticLegend(false)}
/>
</Tabber>
</form>
</div>
</FancyScrollbar>
@ -169,7 +183,7 @@ class AxesOptions extends Component {
}
}
const {arrayOf, func, shape, string} = PropTypes
const {arrayOf, bool, func, shape, string} = PropTypes
AxesOptions.defaultProps = {
axes: {
@ -193,6 +207,8 @@ AxesOptions.propTypes = {
defaultYLabel: string,
}),
}).isRequired,
onToggleStaticLegend: func.isRequired,
staticLegend: bool,
handleUpdateAxes: func.isRequired,
}

View File

@ -14,6 +14,7 @@ import * as queryModifiers from 'src/utils/queryTransitions'
import defaultQueryConfig from 'src/utils/defaultQueryConfig'
import {buildQuery} from 'utils/influxql'
import {getQueryConfig} from 'shared/apis'
import {IS_STATIC_LEGEND} from 'src/shared/constants'
import {
removeUnselectedTemplateValues,
@ -28,7 +29,7 @@ class CellEditorOverlay extends Component {
constructor(props) {
super(props)
const {cell: {queries}, sources} = props
const {cell: {queries, legend}, sources} = props
let source = _.get(queries, ['0', 'source'], null)
source = sources.find(s => s.links.self === source) || props.source
@ -45,6 +46,7 @@ class CellEditorOverlay extends Component {
queriesWorkingDraft,
activeQueryIndex: 0,
isDisplayOptionsTabActive: false,
staticLegend: IS_STATIC_LEGEND(legend),
}
}
@ -102,8 +104,7 @@ class CellEditorOverlay extends Component {
}
handleSaveCell = () => {
const {queriesWorkingDraft} = this.state
const {queriesWorkingDraft, staticLegend} = this.state
const {cell, singleStatColors, gaugeColors} = this.props
const queries = queriesWorkingDraft.map(q => {
@ -131,6 +132,12 @@ class CellEditorOverlay extends Component {
...cell,
queries,
colors,
legend: staticLegend
? {
type: 'static',
orientation: 'bottom',
}
: {},
})
}
@ -142,6 +149,10 @@ class CellEditorOverlay extends Component {
this.setState({activeQueryIndex})
}
handleToggleStaticLegend = staticLegend => () => {
this.setState({staticLegend})
}
handleSetQuerySource = source => {
const queriesWorkingDraft = this.state.queriesWorkingDraft.map(q => ({
..._.cloneDeep(q),
@ -251,6 +262,7 @@ class CellEditorOverlay extends Component {
activeQueryIndex,
isDisplayOptionsTabActive,
queriesWorkingDraft,
staticLegend,
} = this.state
const queryActions = {
@ -282,6 +294,7 @@ class CellEditorOverlay extends Component {
autoRefresh={autoRefresh}
queryConfigs={queriesWorkingDraft}
editQueryStatus={editQueryStatus}
staticLegend={staticLegend}
/>
<CEOBottom>
<OverlayControls
@ -296,7 +309,11 @@ class CellEditorOverlay extends Component {
onClickDisplayOptions={this.handleClickDisplayOptionsTab}
/>
{isDisplayOptionsTabActive
? <DisplayOptions queryConfigs={queriesWorkingDraft} />
? <DisplayOptions
queryConfigs={queriesWorkingDraft}
onToggleStaticLegend={this.handleToggleStaticLegend}
staticLegend={staticLegend}
/>
: <QueryMaker
source={this.getSource()}
templates={templates}

View File

@ -35,15 +35,19 @@ class DisplayOptions extends Component {
}
renderOptions = () => {
const {cell: {type}} = this.props
const {cell: {type}, staticLegend, onToggleStaticLegend} = this.props
switch (type) {
case 'gauge':
return <GaugeOptions />
case 'single-stat':
return <SingleStatOptions />
default:
return <AxesOptions />
return (
<AxesOptions
onToggleStaticLegend={onToggleStaticLegend}
staticLegend={staticLegend}
/>
)
}
}
@ -56,7 +60,8 @@ class DisplayOptions extends Component {
)
}
}
const {arrayOf, shape, string} = PropTypes
const {arrayOf, bool, func, shape, string} = PropTypes
DisplayOptions.propTypes = {
cell: shape({
@ -70,6 +75,8 @@ DisplayOptions.propTypes = {
}),
}).isRequired,
queryConfigs: arrayOf(shape()).isRequired,
onToggleStaticLegend: func.isRequired,
staticLegend: bool,
}
const mapStateToProps = ({cellEditorOverlay: {cell, cell: {axes}}}) => ({

View File

@ -2,7 +2,7 @@ import React, {PropTypes} from 'react'
import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip'
export const Tabber = ({labelText, children, tipID, tipContent}) =>
<div className="form-group col-sm-6">
<div className="form-group col-md-6">
<label>
{labelText}
{tipID

View File

@ -18,6 +18,7 @@ const DashVisualization = (
queryConfigs,
editQueryStatus,
resizerTopHeight,
staticLegend,
singleStatColors,
},
{source: {links: {proxy}}}
@ -37,13 +38,14 @@ const DashVisualization = (
autoRefresh={autoRefresh}
editQueryStatus={editQueryStatus}
resizerTopHeight={resizerTopHeight}
staticLegend={staticLegend}
/>
</div>
</div>
)
}
const {arrayOf, func, number, shape, string} = PropTypes
const {arrayOf, bool, func, number, shape, string} = PropTypes
DashVisualization.propTypes = {
type: string.isRequired,
@ -79,6 +81,7 @@ DashVisualization.propTypes = {
value: number.isRequired,
}).isRequired
),
staticLegend: bool,
}
DashVisualization.contextTypes = {

View File

@ -5,7 +5,13 @@ import AnnotationSpan from 'shared/components/AnnotationSpan'
import * as schema from 'shared/schemas'
const Annotation = ({dygraph, annotation, mode, lastUpdated}) =>
const Annotation = ({
dygraph,
annotation,
mode,
lastUpdated,
staticLegendHeight,
}) =>
<div>
{annotation.startTime === annotation.endTime
? <AnnotationPoint
@ -19,6 +25,7 @@ const Annotation = ({dygraph, annotation, mode, lastUpdated}) =>
annotation={annotation}
mode={mode}
dygraph={dygraph}
staticLegendHeight={staticLegendHeight}
/>}
</div>
@ -29,6 +36,7 @@ Annotation.propTypes = {
lastUpdated: number,
annotation: schema.annotation.isRequired,
dygraph: shape({}).isRequired,
staticLegendHeight: number,
}
export default Annotation

View File

@ -186,7 +186,7 @@ class AnnotationSpan extends React.Component {
}
render() {
const {annotation, dygraph} = this.props
const {annotation, dygraph, staticLegendHeight} = this.props
const {isDragging} = this.state
return (
@ -195,6 +195,7 @@ class AnnotationSpan extends React.Component {
annotation={annotation}
dygraph={dygraph}
active={!!isDragging}
staticLegendHeight={staticLegendHeight}
/>
{this.renderLeftMarker(annotation.startTime, dygraph)}
{this.renderRightMarker(annotation.endTime, dygraph)}
@ -207,6 +208,7 @@ AnnotationSpan.propTypes = {
annotation: schema.annotation.isRequired,
mode: PropTypes.string.isRequired,
dygraph: PropTypes.shape({}).isRequired,
staticLegendHeight: PropTypes.number.isRequired,
updateAnnotationAsync: PropTypes.func.isRequired,
updateAnnotation: PropTypes.func.isRequired,
}

View File

@ -3,7 +3,7 @@ import React, {PropTypes} from 'react'
import {DYGRAPH_CONTAINER_MARGIN} from 'shared/constants'
import * as schema from 'shared/schemas'
const windowDimensions = (anno, dygraph) => {
const windowDimensions = (anno, dygraph, staticLegendHeight) => {
// TODO: export and test this function
const [startX, endX] = dygraph.xAxisRange()
const startTime = Math.max(+anno.startTime, startX)
@ -16,23 +16,29 @@ const windowDimensions = (anno, dygraph) => {
const windowLeftXCoord =
Math.min(windowStartXCoord, windowEndXCoord) + DYGRAPH_CONTAINER_MARGIN
const height = staticLegendHeight
? `calc(100% - ${staticLegendHeight + 36}px)`
: 'calc(100% - 36px)'
return {
left: `${windowLeftXCoord}px`,
width: `${windowWidth}px`,
height,
}
}
const AnnotationWindow = ({annotation, dygraph, active}) =>
const AnnotationWindow = ({annotation, dygraph, active, staticLegendHeight}) =>
<div
className={`annotation-window${active ? ' active' : ''}`}
style={windowDimensions(annotation, dygraph)}
style={windowDimensions(annotation, dygraph, staticLegendHeight)}
/>
const {bool, shape} = PropTypes
const {bool, number, shape} = PropTypes
AnnotationWindow.propTypes = {
annotation: schema.annotation.isRequired,
dygraph: shape({}).isRequired,
staticLegendHeight: number,
active: bool,
}

View File

@ -41,6 +41,7 @@ class Annotations extends Component {
handleAddingAnnotationSuccess,
handleMouseEnterTempAnnotation,
handleMouseLeaveTempAnnotation,
staticLegendHeight,
} = this.props
const annotations = visibleAnnotations(
@ -64,6 +65,7 @@ class Annotations extends Component {
isTempHovering={isTempHovering}
onMouseEnterTempAnnotation={handleMouseEnterTempAnnotation}
onMouseLeaveTempAnnotation={handleMouseLeaveTempAnnotation}
staticLegendHeight={staticLegendHeight}
/>}
{annotations.map(a =>
<Annotation
@ -71,6 +73,7 @@ class Annotations extends Component {
mode={mode}
annotation={a}
dygraph={dygraph}
staticLegendHeight={staticLegendHeight}
lastUpdated={lastUpdated}
/>
)}
@ -79,7 +82,7 @@ class Annotations extends Component {
}
}
const {arrayOf, bool, func, shape, string} = PropTypes
const {arrayOf, bool, func, number, shape, string} = PropTypes
Annotations.propTypes = {
annotations: arrayOf(schema.annotation),
@ -92,6 +95,7 @@ Annotations.propTypes = {
handleAddingAnnotationSuccess: func.isRequired,
handleMouseEnterTempAnnotation: func.isRequired,
handleMouseLeaveTempAnnotation: func.isRequired,
staticLegendHeight: number,
}
const mapStateToProps = ({

View File

@ -7,6 +7,7 @@ import NanoDate from 'nano-date'
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 getRange, {getStackedRange} from 'shared/parsing/getRangeForDygraph'
@ -24,7 +25,6 @@ import {
highlightSeriesOpts,
} from 'src/shared/graphs/helpers'
const {LINEAR, LOG, BASE_10, BASE_2} = DISPLAY_OPTIONS
import {ADDING, EDITING} from 'src/shared/annotations/helpers'
class Dygraph extends Component {
constructor(props) {
@ -32,6 +32,7 @@ class Dygraph extends Component {
this.state = {
isSynced: false,
isHidden: true,
staticLegendHeight: null,
}
}
@ -294,11 +295,24 @@ class Dygraph extends Component {
handleAnnotationsRef = ref => (this.annotationsRef = ref)
render() {
const {isHidden} = this.state
const {mode} = this.props
handleReceiveStaticLegendHeight = staticLegendHeight => {
this.setState({staticLegendHeight})
}
const hideLegend = mode === EDITING || mode === ADDING ? true : isHidden
render() {
const {isHidden, staticLegendHeight} = this.state
const {staticLegend} = this.props
let dygraphStyle = {...this.props.containerStyle, zIndex: '2'}
if (staticLegend) {
const cellVerticalPadding = 16
dygraphStyle = {
...this.props.containerStyle,
zIndex: '2',
height: `calc(100% - ${staticLegendHeight + cellVerticalPadding}px)`,
}
}
return (
<div className="dygraph-child" onMouseLeave={this.deselectCrosshair}>
@ -306,10 +320,11 @@ class Dygraph extends Component {
<Annotations
dygraph={this.dygraph}
annotationsRef={this.handleAnnotationsRef}
staticLegendHeight={staticLegendHeight}
/>}
{this.dygraph &&
<DygraphLegend
isHidden={hideLegend}
isHidden={isHidden}
dygraph={this.dygraph}
onHide={this.handleHideLegend}
onShow={this.handleShowLegend}
@ -320,8 +335,15 @@ class Dygraph extends Component {
this.props.dygraphRef(r)
}}
className="dygraph-child-container"
style={{...this.props.containerStyle, zIndex: '2'}}
style={dygraphStyle}
/>
{staticLegend &&
<StaticLegend
dygraph={this.dygraph}
handleReceiveStaticLegendHeight={
this.handleReceiveStaticLegendHeight
}
/>}
</div>
)
}
@ -349,6 +371,9 @@ Dygraph.defaultProps = {
overrideLineColors: null,
dygraphRef: () => {},
onZoom: () => {},
staticLegend: {
type: null,
},
}
Dygraph.propTypes = {
@ -367,6 +392,7 @@ Dygraph.propTypes = {
containerStyle: shape({}),
isGraphFilled: bool,
isBarGraph: bool,
staticLegend: bool,
overrideLineColors: array,
dygraphSeries: shape({}).isRequired,
ruleValues: shape({

View File

@ -3,12 +3,7 @@ import _ from 'lodash'
import classnames from 'classnames'
import uuid from 'node-uuid'
import {makeLegendStyles} from 'shared/graphs/helpers'
const removeMeasurement = (label = '') => {
const [measurement] = label.match(/^(.*)[.]/g) || ['']
return label.replace(measurement, '')
}
import {makeLegendStyles, removeMeasurement} from 'shared/graphs/helpers'
class DygraphLegend extends Component {
state = {

View File

@ -4,7 +4,7 @@ import ReactTooltip from 'react-tooltip'
const GraphTips = React.createClass({
render() {
const graphTipsText =
'<h1>Graph Tips:</h1><p><code>Click + Drag</code> Zoom in (X or Y)<br/><code>Shift + Click</code> Pan Graph Window<br/><code>Double Click</code> Reset Graph Window</p>'
'<h1>Graph Tips:</h1><p><code>Click + Drag</code> Zoom in (X or Y)<br/><code>Shift + Click</code> Pan Graph Window<br/><code>Double Click</code> Reset Graph Window</p><h1>Static Legend Tips:</h1><p><code>Click</code>Focus on single Series<br/><code>Shift + Click</code> Show/Hide single Series</p>'
return (
<div
className="graph-tips"

View File

@ -3,6 +3,7 @@ import WidgetCell from 'shared/components/WidgetCell'
import LayoutCell from 'shared/components/LayoutCell'
import RefreshingGraph from 'shared/components/RefreshingGraph'
import {buildQueriesForLayouts} from 'utils/buildQueriesForLayouts'
import {IS_STATIC_LEGEND} from 'src/shared/constants'
import _ from 'lodash'
@ -39,7 +40,7 @@ const Layout = (
{
host,
cell,
cell: {h, axes, type, colors},
cell: {h, axes, type, colors, legend},
source,
sources,
onZoom,
@ -76,6 +77,7 @@ const Layout = (
inView={cell.inView}
axes={axes}
type={type}
staticLegend={IS_STATIC_LEGEND(legend)}
cellHeight={h}
onZoom={onZoom}
sources={sources}

View File

@ -54,6 +54,7 @@ class LineGraph extends Component {
isGraphFilled,
showSingleStat,
displayOptions,
staticLegend,
underlayCallback,
overrideLineColors,
isFetchingInitially,
@ -120,6 +121,7 @@ class LineGraph extends Component {
setResolution={setResolution}
overrideLineColors={lineColors}
containerStyle={containerStyle}
staticLegend={staticLegend}
isGraphFilled={showSingleStat ? false : isGraphFilled}
/>
{showSingleStat
@ -155,6 +157,7 @@ LineGraph.defaultProps = {
underlayCallback: () => {},
isGraphFilled: true,
overrideLineColors: null,
staticLegend: false,
}
LineGraph.propTypes = {
@ -174,6 +177,7 @@ LineGraph.propTypes = {
underlayCallback: func,
isGraphFilled: bool,
isBarGraph: bool,
staticLegend: bool,
overrideLineColors: array,
showSingleStat: bool,
displayOptions: shape({

View File

@ -117,6 +117,7 @@ class NewAnnotation extends Component {
isTempHovering,
tempAnnotation,
tempAnnotation: {startTime, endTime},
staticLegendHeight,
} = this.props
const {isMouseOver} = this.state
@ -141,6 +142,7 @@ class NewAnnotation extends Component {
annotation={tempAnnotation}
dygraph={dygraph}
active={true}
staticLegendHeight={staticLegendHeight}
/>}
<div
className={classnames('new-annotation', {
@ -178,7 +180,7 @@ class NewAnnotation extends Component {
}
}
const {bool, func, shape, string} = PropTypes
const {bool, func, number, shape, string} = PropTypes
NewAnnotation.contextTypes = {
source: shape({
@ -198,6 +200,7 @@ NewAnnotation.propTypes = {
onUpdateAnnotation: func.isRequired,
onMouseEnterTempAnnotation: func.isRequired,
onMouseLeaveTempAnnotation: func.isRequired,
staticLegendHeight: number,
}
const mdtp = {

View File

@ -24,6 +24,7 @@ const RefreshingGraph = ({
cellHeight,
autoRefresh,
resizerTopHeight,
staticLegend,
manualRefresh, // when changed, re-mounts the component
synchronizer,
resizeCoords,
@ -95,6 +96,7 @@ const RefreshingGraph = ({
isBarGraph={type === 'bar'}
synchronizer={synchronizer}
resizeCoords={resizeCoords}
staticLegend={staticLegend}
displayOptions={displayOptions}
editQueryStatus={editQueryStatus}
grabDataForDownload={grabDataForDownload}
@ -119,6 +121,7 @@ RefreshingGraph.propTypes = {
axes: shape(),
queries: arrayOf(shape()).isRequired,
editQueryStatus: func,
staticLegend: bool,
onZoom: func,
resizeCoords: shape(),
grabDataForDownload: func,
@ -137,6 +140,7 @@ RefreshingGraph.propTypes = {
RefreshingGraph.defaultProps = {
manualRefresh: 0,
staticLegend: false,
inView: true,
}

View File

@ -0,0 +1,113 @@
import React, {PropTypes, Component} from 'react'
import _ from 'lodash'
import uuid from 'node-uuid'
import {removeMeasurement} from 'shared/graphs/helpers'
const staticLegendItemClassname = (visibilities, i, hoverEnabled) => {
if (visibilities.length) {
return `${hoverEnabled
? 'static-legend--item'
: 'static-legend--single'}${visibilities[i] ? '' : ' disabled'}`
}
// all series are visible to match expected initial state
return 'static-legend--item'
}
class StaticLegend extends Component {
constructor(props) {
super(props)
}
state = {
visibilities: [],
clickStatus: false,
}
componentDidMount = () => {
const {height} = this.staticLegendRef.getBoundingClientRect()
this.props.handleReceiveStaticLegendHeight(height)
}
componentDidUpdate = () => {
const {height} = this.staticLegendRef.getBoundingClientRect()
this.props.handleReceiveStaticLegendHeight(height)
}
componentWillUnmount = () => {
this.props.handleReceiveStaticLegendHeight(null)
}
handleClick = i => e => {
const visibilities = this.props.dygraph.visibility()
const clickStatus = this.state.clickStatus
if (e.shiftKey || e.metaKey) {
visibilities[i] = !visibilities[i]
this.props.dygraph.setVisibility(visibilities)
this.setState({visibilities})
return
}
const prevClickStatus = clickStatus && visibilities[i]
const newVisibilities = prevClickStatus
? _.map(visibilities, () => true)
: _.map(visibilities, () => false)
newVisibilities[i] = true
this.props.dygraph.setVisibility(newVisibilities)
this.setState({
visibilities: newVisibilities,
clickStatus: !prevClickStatus,
})
}
render() {
const {dygraph} = this.props
const {visibilities} = this.state
const labels = dygraph ? _.drop(dygraph.getLabels()) : []
const colors = dygraph
? _.map(labels, l => dygraph.attributes_.series_[l].options.color)
: []
const hoverEnabled = labels.length > 1
return (
<div
className="static-legend"
ref={s => {
this.staticLegendRef = s
}}
>
{_.map(labels, (v, i) =>
<div
className={staticLegendItemClassname(visibilities, i, hoverEnabled)}
key={uuid.v4()}
onMouseDown={this.handleClick(i)}
>
<div
className="static-legend--dot"
style={{backgroundColor: colors[i]}}
/>
<span style={{color: colors[i]}}>
{removeMeasurement(v)}
</span>
</div>
)}
</div>
)
}
}
const {shape, func} = PropTypes
StaticLegend.propTypes = {
sharedLegend: shape({}),
dygraph: shape({}),
handleReceiveStaticLegendHeight: func.isRequired,
}
export default StaticLegend

View File

@ -1,3 +1,5 @@
import _ from 'lodash'
export const PERMISSIONS = {
ViewAdmin: {
description: 'Can view or edit admin screens',
@ -426,6 +428,9 @@ export const DEFAULT_SOURCE = {
metaUrl: '',
}
export const IS_STATIC_LEGEND = legend =>
_.get(legend, 'type', false) === 'static'
export const linksLink = '/chronograf/v1'
export const cellSupportsAnnotations = cellType => {

View File

@ -173,6 +173,12 @@ export const makeLegendStyles = (graph, legend, pageX) => {
}
}
// globally matches anything that ends in a '.'
export const removeMeasurement = (label = '') => {
const [measurement] = label.match(/^(.*)[.]/g) || ['']
return label.replace(measurement, '')
}
export const OPTIONS = {
rightGap: 0,
axisLineWidth: 2,

View File

@ -6,6 +6,7 @@ export const fixtureStatusPageCells = [
y: 0,
w: 12,
h: 4,
legend: {},
name: 'Alert Events per Day Last 30 Days',
queries: [
{
@ -54,6 +55,7 @@ export const fixtureStatusPageCells = [
y: 5,
w: 6.5,
h: 6,
legend: {},
queries: [
{
query: '',
@ -80,6 +82,7 @@ export const fixtureStatusPageCells = [
y: 5,
w: 3,
h: 6,
legend: {},
queries: [
{
query: '',
@ -106,6 +109,7 @@ export const fixtureStatusPageCells = [
y: 5,
w: 2.5,
h: 6,
legend: {},
queries: [
{
query: '',

View File

@ -52,6 +52,7 @@
@import 'components/page-header-dropdown';
@import 'components/page-header-editable';
@import 'components/page-spinner';
@import 'components/static-legend';
@import 'components/query-maker';
@import 'components/react-tooltips';
@import 'components/redacted-input';

View File

@ -207,7 +207,6 @@ $timestamp-font-weight: 600;
position: absolute;
top: 8px;
background: linear-gradient(to bottom, $window15 0%, $window0 100%);
height: calc(100% - 36px);
border-top: 2px dotted $window35;
z-index: 1;

View File

@ -43,13 +43,13 @@ $tooltip-code-color: $c-potassium;
h1 {
font-size: 14px;
font-weight: 600;
margin: 0 0 8px 0;
margin: 8px 0;
line-height: 1.125em;
letter-spacing: 0;
font-family: $default-font;
&:only-child {
margin: 0;
&:first-of-type {
margin-top: 0;
}
}

View File

@ -0,0 +1,79 @@
/*
static Legend
------------------------------------------------------------------------------
Seen in a dashboard cell, below the graph
NOTE: Styles for the parent are stored in Javascript, in staticLegend.js
*/
.static-legend {
position: absolute;
width: calc(100% - 32px);
bottom: 8px;
left: 16px;
display: flex;
padding-top: 8px;
align-items: flex-end;
flex-wrap: wrap;
max-height: 50%;
overflow: auto;
@include custom-scrollbar($g3-castle,$g6-smoke);
}
.static-legend--dot {
display: inline-block;
vertical-align: middle;
margin-right: 4px;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: $g20-white;
}
.static-legend--item,
.static-legend--single {
height: 22px;
line-height: 22px;
white-space: nowrap;
background-color: $g4-onyx;
border-radius: 3px;
color: $g20-white;
font-size: 12px;
font-weight: 600;
padding: 0 7px;
margin: 1px;
}
.static-legend--item {
transition: background-color 0.25s ease, color 0.25s ease;
span,
.static-legend--dot {
opacity: 0.8;
transition: opacity 0.25s ease;
}
&:hover {
cursor: pointer;
background-color: $g6-smoke;
span,
.static-legend--dot {
opacity: 1;
}
}
&.disabled {
background-color: $g1-raven;
font-style: italic;
span,
.static-legend--dot {
opacity: 0.35;
}
&:hover {
background-color: $g2-kevlar;
span,
.static-legend--dot {
opacity: 0.65;
}
}
}
}