diff --git a/CHANGELOG.md b/CHANGELOG.md
index e72b29dc8..37b199ef0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@
1. [#2698](https://github.com/influxdata/chronograf/pull/2698): Improve clarity of terminology surrounding InfluxDB & Kapacitor connections
1. [#2746](https://github.com/influxdata/chronograf/pull/2746): Separate saving TICKscript from exiting editor page
1. [#2774](https://github.com/influxdata/chronograf/pull/2774): Enable Save (⌘ + Enter) and Cancel (Escape) hotkeys in Cell Editor Overlay
+1. [#2788](https://github.com/influxdata/chronograf/pull/2788): Enable customization of Single Stat graph's "Base Color"
### Bug Fixes
1. [#2684](https://github.com/influxdata/chronograf/pull/2684): Fix TICKscript Sensu alerts when no group by tags selected
@@ -23,6 +24,7 @@
1. [#2756](https://github.com/influxdata/chronograf/pull/2756): Display only 200 most recent TICKscript log messages and prevent overlapping
1. [#2757](https://github.com/influxdata/chronograf/pull/2757): Added "TO" field to kapacitor SMTP config, and improved error messages for config saving and testing
1. [#2761](https://github.com/influxdata/chronograf/pull/2761): Remove cli options from sysvinit service file
+1. [#2788](https://github.com/influxdata/chronograf/pull/2788): Fix disappearance of text in Single Stat graphs during editing
1. [#2780](https://github.com/influxdata/chronograf/pull/2780): Fix routing on alert save
## v1.4.0.1 [2017-1-9]
diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js
index 8432c8f31..5b82c428d 100644
--- a/ui/src/dashboards/components/CellEditorOverlay.js
+++ b/ui/src/dashboards/components/CellEditorOverlay.js
@@ -28,9 +28,10 @@ import {
DEFAULT_VALUE_MIN,
DEFAULT_VALUE_MAX,
GAUGE_COLORS,
- SINGLE_STAT_TEXT,
- SINGLE_STAT_BG,
- validateColors,
+ validateGaugeColors,
+ validateSingleStatColors,
+ getSingleStatType,
+ stringifyColorValues,
} from 'src/dashboards/constants/gaugeColors'
class CellEditorOverlay extends Component {
@@ -49,7 +50,8 @@ class CellEditorOverlay extends Component {
source,
}))
)
- const colorsTypeContainsText = _.some(colors, {type: SINGLE_STAT_TEXT})
+
+ const singleStatType = getSingleStatType(colors)
this.state = {
cellWorkingName: name,
@@ -58,8 +60,9 @@ class CellEditorOverlay extends Component {
activeQueryIndex: 0,
isDisplayOptionsTabActive: false,
axes,
- colorSingleStatText: colorsTypeContainsText,
- colors: validateColors(colors, type, colorsTypeContainsText),
+ singleStatType,
+ gaugeColors: validateGaugeColors(colors),
+ singleStatColors: validateSingleStatColors(colors, singleStatType),
}
}
@@ -80,27 +83,21 @@ class CellEditorOverlay extends Component {
this.overlayRef.focus()
}
- handleAddThreshold = () => {
- const {colors, cellWorkingType} = this.state
- const sortedColors = _.sortBy(colors, color => Number(color.value))
+ handleAddGaugeThreshold = () => {
+ const {gaugeColors} = this.state
+ const sortedColors = _.sortBy(gaugeColors, color => color.value)
if (sortedColors.length <= MAX_THRESHOLDS) {
const randomColor = _.random(0, GAUGE_COLORS.length - 1)
- const maxValue =
- cellWorkingType === 'gauge'
- ? Number(sortedColors[sortedColors.length - 1].value)
- : DEFAULT_VALUE_MAX
- const minValue =
- cellWorkingType === 'gauge'
- ? Number(sortedColors[0].value)
- : DEFAULT_VALUE_MIN
+ const maxValue = sortedColors[sortedColors.length - 1].value
+ const minValue = sortedColors[0].value
- const colorsValues = _.mapValues(colors, 'value')
+ const colorsValues = _.mapValues(gaugeColors, 'value')
let randomValue
do {
- randomValue = `${_.round(_.random(minValue, maxValue, true), 2)}`
+ randomValue = _.round(_.random(minValue, maxValue, true), 2)
} while (_.includes(colorsValues, randomValue))
const newThreshold = {
@@ -111,68 +108,134 @@ class CellEditorOverlay extends Component {
name: GAUGE_COLORS[randomColor].name,
}
- this.setState({colors: [...colors, newThreshold]})
+ this.setState({gaugeColors: [...gaugeColors, newThreshold]})
}
}
+ handleAddSingleStatThreshold = () => {
+ const {singleStatColors, singleStatType} = this.state
+
+ const randomColor = _.random(0, GAUGE_COLORS.length - 1)
+
+ const maxValue = DEFAULT_VALUE_MIN
+ const minValue = DEFAULT_VALUE_MAX
+
+ let randomValue = _.round(_.random(minValue, maxValue, true), 2)
+
+ if (singleStatColors.length > 0) {
+ const colorsValues = _.mapValues(singleStatColors, 'value')
+ do {
+ randomValue = _.round(_.random(minValue, maxValue, true), 2)
+ } while (_.includes(colorsValues, randomValue))
+ }
+
+ const newThreshold = {
+ type: singleStatType,
+ id: uuid.v4(),
+ value: randomValue,
+ hex: GAUGE_COLORS[randomColor].hex,
+ name: GAUGE_COLORS[randomColor].name,
+ }
+
+ this.setState({singleStatColors: [...singleStatColors, newThreshold]})
+ }
+
handleDeleteThreshold = threshold => () => {
- const {colors} = this.state
+ const {cellWorkingType} = this.state
- const newColors = colors.filter(color => color.id !== threshold.id)
+ if (cellWorkingType === 'gauge') {
+ const gaugeColors = this.state.gaugeColors.filter(
+ color => color.id !== threshold.id
+ )
- this.setState({colors: newColors})
+ this.setState({gaugeColors})
+ }
+
+ if (cellWorkingType === 'single-stat') {
+ const singleStatColors = this.state.singleStatColors.filter(
+ color => color.id !== threshold.id
+ )
+
+ this.setState({singleStatColors})
+ }
}
handleChooseColor = threshold => chosenColor => {
- const {colors} = this.state
+ const {cellWorkingType} = this.state
- const newColors = colors.map(
- color =>
- color.id === threshold.id
- ? {...color, hex: chosenColor.hex, name: chosenColor.name}
- : color
- )
+ if (cellWorkingType === 'gauge') {
+ const gaugeColors = this.state.gaugeColors.map(
+ color =>
+ color.id === threshold.id
+ ? {...color, hex: chosenColor.hex, name: chosenColor.name}
+ : color
+ )
- this.setState({colors: newColors})
+ this.setState({gaugeColors})
+ }
+
+ if (cellWorkingType === 'single-stat') {
+ const singleStatColors = this.state.singleStatColors.map(
+ color =>
+ color.id === threshold.id
+ ? {...color, hex: chosenColor.hex, name: chosenColor.name}
+ : color
+ )
+
+ this.setState({singleStatColors})
+ }
}
- handleUpdateColorValue = (threshold, newValue) => {
- const {colors} = this.state
- const newColors = colors.map(
- color => (color.id === threshold.id ? {...color, value: newValue} : color)
- )
- this.setState({colors: newColors})
+ handleUpdateColorValue = (threshold, value) => {
+ const {cellWorkingType} = this.state
+
+ if (cellWorkingType === 'gauge') {
+ const gaugeColors = this.state.gaugeColors.map(
+ color => (color.id === threshold.id ? {...color, value} : color)
+ )
+
+ this.setState({gaugeColors})
+ }
+
+ if (cellWorkingType === 'single-stat') {
+ const singleStatColors = this.state.singleStatColors.map(
+ color => (color.id === threshold.id ? {...color, value} : color)
+ )
+
+ this.setState({singleStatColors})
+ }
}
- handleValidateColorValue = (threshold, e) => {
- const {colors, cellWorkingType} = this.state
- const sortedColors = _.sortBy(colors, color => Number(color.value))
- const thresholdValue = Number(threshold.value)
- const targetValueNumber = Number(e.target.value)
+ handleValidateColorValue = (threshold, targetValue) => {
+ const {gaugeColors, singleStatColors, cellWorkingType} = this.state
+ const thresholdValue = threshold.value
let allowedToUpdate = false
if (cellWorkingType === 'single-stat') {
// If type is single-stat then value only has to be unique
- return !sortedColors.some(color => color.value === e.target.value)
+ const sortedColors = _.sortBy(singleStatColors, color => color.value)
+ return !sortedColors.some(color => color.value === targetValue)
}
- const minValue = Number(sortedColors[0].value)
- const maxValue = Number(sortedColors[sortedColors.length - 1].value)
+ const sortedColors = _.sortBy(gaugeColors, color => color.value)
+
+ const minValue = sortedColors[0].value
+ const maxValue = sortedColors[sortedColors.length - 1].value
// If lowest value, make sure it is less than the next threshold
if (thresholdValue === minValue) {
- const nextValue = Number(sortedColors[1].value)
- allowedToUpdate = targetValueNumber < nextValue
+ const nextValue = sortedColors[1].value
+ allowedToUpdate = targetValue < nextValue
}
// If highest value, make sure it is greater than the previous threshold
if (thresholdValue === maxValue) {
- const previousValue = Number(sortedColors[sortedColors.length - 2].value)
- allowedToUpdate = previousValue < targetValueNumber
+ const previousValue = sortedColors[sortedColors.length - 2].value
+ allowedToUpdate = previousValue < targetValue
}
// If not min or max, make sure new value is greater than min, less than max, and unique
if (thresholdValue !== minValue && thresholdValue !== maxValue) {
- const greaterThanMin = targetValueNumber > minValue
- const lessThanMax = targetValueNumber < maxValue
+ const greaterThanMin = targetValue > minValue
+ const lessThanMax = targetValue < maxValue
const colorsWithoutMinOrMax = sortedColors.slice(
1,
@@ -180,7 +243,7 @@ class CellEditorOverlay extends Component {
)
const isUnique = !colorsWithoutMinOrMax.some(
- color => color.value === e.target.value
+ color => color.value === targetValue
)
allowedToUpdate = greaterThanMin && lessThanMax && isUnique
@@ -189,16 +252,15 @@ class CellEditorOverlay extends Component {
return allowedToUpdate
}
- handleToggleSingleStatText = () => {
- const {colors, colorSingleStatText} = this.state
- const formattedColors = colors.map(color => ({
+ handleToggleSingleStatType = type => () => {
+ const singleStatColors = this.state.singleStatColors.map(color => ({
...color,
- type: colorSingleStatText ? SINGLE_STAT_BG : SINGLE_STAT_TEXT,
+ type,
}))
this.setState({
- colorSingleStatText: !colorSingleStatText,
- colors: formattedColors,
+ singleStatType: type,
+ singleStatColors,
})
}
@@ -298,7 +360,8 @@ class CellEditorOverlay extends Component {
cellWorkingType: type,
cellWorkingName: name,
axes,
- colors,
+ gaugeColors,
+ singleStatColors,
} = this.state
const {cell} = this.props
@@ -314,6 +377,13 @@ class CellEditorOverlay extends Component {
}
})
+ let colors = []
+ if (type === 'gauge') {
+ colors = stringifyColorValues(gaugeColors)
+ } else if (type === 'single-stat' || type === 'line-plus-single-stat') {
+ colors = stringifyColorValues(singleStatColors)
+ }
+
this.props.onSave({
...cell,
name,
@@ -324,14 +394,8 @@ class CellEditorOverlay extends Component {
})
}
- handleSelectGraphType = graphType => () => {
- const {colors, colorSingleStatText} = this.state
- const validatedColors = validateColors(
- colors,
- graphType,
- colorSingleStatText
- )
- this.setState({cellWorkingType: graphType, colors: validatedColors})
+ handleSelectGraphType = cellWorkingType => () => {
+ this.setState({cellWorkingType})
}
handleClickDisplayOptionsTab = isDisplayOptionsTabActive => () => {
@@ -474,13 +538,14 @@ class CellEditorOverlay extends Component {
const {
axes,
- colors,
+ gaugeColors,
+ singleStatColors,
activeQueryIndex,
cellWorkingName,
cellWorkingType,
isDisplayOptionsTabActive,
queriesWorkingDraft,
- colorSingleStatText,
+ singleStatType,
} = this.state
const queryActions = {
@@ -492,6 +557,9 @@ class CellEditorOverlay extends Component {
(!!query.measurement && !!query.database && !!query.fields.length) ||
!!query.rawText
+ const visualizationColors =
+ cellWorkingType === 'gauge' ? gaugeColors : singleStatColors
+
return (
{
const {
- colors,
+ gaugeColors,
+ singleStatColors,
onSetBase,
onSetScale,
onSetLabel,
@@ -43,13 +44,14 @@ class DisplayOptions extends Component {
onSetPrefixSuffix,
onSetYAxisBoundMin,
onSetYAxisBoundMax,
- onAddThreshold,
+ onAddGaugeThreshold,
+ onAddSingleStatThreshold,
onDeleteThreshold,
onChooseColor,
onValidateColorValue,
onUpdateColorValue,
- colorSingleStatText,
- onToggleSingleStatText,
+ singleStatType,
+ onToggleSingleStatType,
onSetSuffix,
} = this.props
const {axes, axes: {y: {suffix}}} = this.state
@@ -58,27 +60,27 @@ class DisplayOptions extends Component {
case 'gauge':
return (
)
case 'single-stat':
return (
)
default:
@@ -111,10 +113,11 @@ class DisplayOptions extends Component {
)
}
}
-const {arrayOf, bool, func, shape, string} = PropTypes
+const {arrayOf, func, number, shape, string} = PropTypes
DisplayOptions.propTypes = {
- onAddThreshold: func.isRequired,
+ onAddGaugeThreshold: func.isRequired,
+ onAddSingleStatThreshold: func.isRequired,
onDeleteThreshold: func.isRequired,
onChooseColor: func.isRequired,
onValidateColorValue: func.isRequired,
@@ -129,18 +132,27 @@ DisplayOptions.propTypes = {
onSetLabel: func.isRequired,
onSetBase: func.isRequired,
axes: shape({}).isRequired,
- colors: arrayOf(
+ gaugeColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
- value: string.isRequired,
+ value: number.isRequired,
+ }).isRequired
+ ),
+ singleStatColors: arrayOf(
+ shape({
+ type: string.isRequired,
+ hex: string.isRequired,
+ id: string.isRequired,
+ name: string.isRequired,
+ value: number.isRequired,
}).isRequired
),
queryConfigs: arrayOf(shape()).isRequired,
- colorSingleStatText: bool.isRequired,
- onToggleSingleStatText: func.isRequired,
+ singleStatType: string.isRequired,
+ onToggleSingleStatType: func.isRequired,
}
export default DisplayOptions
diff --git a/ui/src/dashboards/components/GaugeOptions.js b/ui/src/dashboards/components/GaugeOptions.js
index 8a70c37bf..4c42cc8e0 100644
--- a/ui/src/dashboards/components/GaugeOptions.js
+++ b/ui/src/dashboards/components/GaugeOptions.js
@@ -19,7 +19,7 @@ const GaugeOptions = ({
}) => {
const disableMaxColor = colors.length > MIN_THRESHOLDS
const disableAddThreshold = colors.length > MAX_THRESHOLDS
- const sortedColors = _.sortBy(colors, color => Number(color.value))
+ const sortedColors = _.sortBy(colors, color => color.value)
return (
{
+ const {hex, name} = color
+ return {hex, name}
+}
const SingleStatOptions = ({
suffix,
onSetSuffix,
@@ -15,12 +26,12 @@ const SingleStatOptions = ({
onChooseColor,
onValidateColorValue,
onUpdateColorValue,
- colorSingleStatText,
- onToggleSingleStatText,
+ singleStatType,
+ onToggleSingleStatType,
}) => {
const disableAddThreshold = colors.length > MAX_THRESHOLDS
- const sortedColors = _.sortBy(colors, color => Number(color.value))
+ const sortedColors = _.sortBy(colors, color => color.value)
return (
Add Threshold
- {sortedColors.map(color =>
-
+ {sortedColors.map(
+ color =>
+ color.id === SINGLE_STAT_BASE
+ ?
+ :
)}
@@ -54,14 +76,18 @@ const SingleStatOptions = ({
-
Background
-
Text
@@ -83,7 +109,7 @@ const SingleStatOptions = ({
)
}
-const {arrayOf, bool, func, shape, string} = PropTypes
+const {arrayOf, func, number, shape, string} = PropTypes
SingleStatOptions.defaultProps = {
colors: [],
@@ -96,7 +122,7 @@ SingleStatOptions.propTypes = {
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
- value: string.isRequired,
+ value: number.isRequired,
}).isRequired
),
onAddThreshold: func.isRequired,
@@ -104,8 +130,8 @@ SingleStatOptions.propTypes = {
onChooseColor: func.isRequired,
onValidateColorValue: func.isRequired,
onUpdateColorValue: func.isRequired,
- colorSingleStatText: bool.isRequired,
- onToggleSingleStatText: func.isRequired,
+ singleStatType: string.isRequired,
+ onToggleSingleStatType: func.isRequired,
onSetSuffix: func.isRequired,
suffix: string.isRequired,
}
diff --git a/ui/src/dashboards/components/Threshold.js b/ui/src/dashboards/components/Threshold.js
index d01d76c4c..df274f6da 100644
--- a/ui/src/dashboards/components/Threshold.js
+++ b/ui/src/dashboards/components/Threshold.js
@@ -16,14 +16,15 @@ class Threshold extends Component {
handleChangeWorkingValue = e => {
const {threshold, onValidateColorValue, onUpdateColorValue} = this.props
+ const targetValue = Number(e.target.value)
- const valid = onValidateColorValue(threshold, e)
+ const valid = onValidateColorValue(threshold, targetValue)
if (valid) {
- onUpdateColorValue(threshold, e.target.value)
+ onUpdateColorValue(threshold, targetValue)
}
- this.setState({valid, workingValue: e.target.value})
+ this.setState({valid, workingValue: targetValue})
}
handleBlur = () => {
@@ -98,7 +99,7 @@ class Threshold extends Component {
}
}
-const {bool, func, shape, string} = PropTypes
+const {bool, func, number, shape, string} = PropTypes
Threshold.propTypes = {
visualizationType: string.isRequired,
@@ -107,7 +108,7 @@ Threshold.propTypes = {
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
- value: string.isRequired,
+ value: number.isRequired,
}).isRequired,
disableMaxColor: bool,
onChooseColor: func.isRequired,
diff --git a/ui/src/dashboards/components/Visualization.js b/ui/src/dashboards/components/Visualization.js
index 2d6764d70..51d345a21 100644
--- a/ui/src/dashboards/components/Visualization.js
+++ b/ui/src/dashboards/components/Visualization.js
@@ -3,6 +3,8 @@ import RefreshingGraph from 'shared/components/RefreshingGraph'
import buildQueries from 'utils/buildQueriesForGraphs'
import VisualizationName from 'src/dashboards/components/VisualizationName'
+import {stringifyColorValues} from 'src/dashboards/constants/gaugeColors'
+
const DashVisualization = (
{
axes,
@@ -23,7 +25,7 @@ const DashVisualization = (
{
- if (type === 'single-stat') {
- // Single stat colors should all have type of 'text' or 'background'
- const colorType = colorSingleStatText ? SINGLE_STAT_TEXT : SINGLE_STAT_BG
- return colors ? colors.map(color => ({...color, type: colorType})) : null
- }
+export const DEFAULT_SINGLESTAT_COLORS = [
+ {
+ type: SINGLE_STAT_TEXT,
+ hex: GAUGE_COLORS[11].hex,
+ id: SINGLE_STAT_BASE,
+ name: GAUGE_COLORS[11].name,
+ value: 0,
+ },
+]
+
+export const validateSingleStatColors = (colors, type) => {
if (!colors || colors.length === 0) {
- return DEFAULT_COLORS
- }
- if (type === 'gauge') {
- // Gauge colors should have a type of min, any number of thresholds, and a max
- const formatttedColors = _.sortBy(colors, color =>
- Number(color.value)
- ).map(c => ({
- ...c,
- type: COLOR_TYPE_THRESHOLD,
- }))
- formatttedColors[0].type = COLOR_TYPE_MIN
- formatttedColors[formatttedColors.length - 1].type = COLOR_TYPE_MAX
- return formatttedColors
+ return DEFAULT_SINGLESTAT_COLORS
}
- return colors.length >= MIN_THRESHOLDS ? colors : DEFAULT_COLORS
+ let containsBaseColor = false
+
+ const formattedColors = colors.map(color => {
+ if (color.id === SINGLE_STAT_BASE) {
+ // Check for existance of base color
+ containsBaseColor = true
+ return {...color, value: Number(color.value), type}
+ }
+ // Single stat colors should all have type of 'text' or 'background'
+ return {...color, value: Number(color.value), type}
+ })
+
+ const formattedColorsWithBase = [
+ ...formattedColors,
+ DEFAULT_SINGLESTAT_COLORS[0],
+ ]
+
+ return containsBaseColor ? formattedColors : formattedColorsWithBase
+}
+
+export const getSingleStatType = colors => {
+ const type = _.get(colors, ['0', 'type'], false)
+
+ if (type) {
+ if (_.includes([SINGLE_STAT_TEXT, SINGLE_STAT_BG], type)) {
+ return type
+ }
+ }
+
+ return SINGLE_STAT_TEXT
+}
+
+export const validateGaugeColors = colors => {
+ if (!colors || colors.length < MIN_THRESHOLDS) {
+ return DEFAULT_GAUGE_COLORS
+ }
+
+ // Gauge colors should have a type of min, any number of thresholds, and a max
+ const formattedColors = _.sortBy(colors, color =>
+ Number(color.value)
+ ).map(color => ({
+ ...color,
+ value: Number(color.value),
+ type: COLOR_TYPE_THRESHOLD,
+ }))
+
+ formattedColors[0].type = COLOR_TYPE_MIN
+ formattedColors[formattedColors.length - 1].type = COLOR_TYPE_MAX
+
+ return formattedColors
+}
+
+export const stringifyColorValues = colors => {
+ return colors.map(color => ({...color, value: `${color.value}`}))
}
diff --git a/ui/src/shared/components/ColorDropdown.js b/ui/src/shared/components/ColorDropdown.js
index b91058df6..8d8275e60 100644
--- a/ui/src/shared/components/ColorDropdown.js
+++ b/ui/src/shared/components/ColorDropdown.js
@@ -33,11 +33,12 @@ class ColorDropdown extends Component {
render() {
const {visible} = this.state
- const {colors, selected, disabled} = this.props
+ const {colors, selected, disabled, stretchToFit} = this.props
- const dropdownClassNames = visible
- ? 'color-dropdown open'
- : 'color-dropdown'
+ const dropdownClassNames = classnames('color-dropdown', {
+ open: visible,
+ 'color-dropdown--stretch': stretchToFit,
+ })
const toggleClassNames = classnames(
'btn btn-sm btn-default color-dropdown--toggle',
{active: visible, 'color-dropdown__disabled': disabled}
@@ -103,6 +104,7 @@ ColorDropdown.propTypes = {
name: string.isRequired,
})
).isRequired,
+ stretchToFit: bool,
disabled: bool,
}
diff --git a/ui/src/shared/components/GaugeChart.js b/ui/src/shared/components/GaugeChart.js
index bc49f9bf8..a329a89ec 100644
--- a/ui/src/shared/components/GaugeChart.js
+++ b/ui/src/shared/components/GaugeChart.js
@@ -2,7 +2,10 @@ import React, {PropTypes, PureComponent} from 'react'
import lastValues from 'shared/parsing/lastValues'
import Gauge from 'shared/components/Gauge'
-import {DEFAULT_COLORS} from 'src/dashboards/constants/gaugeColors'
+import {
+ DEFAULT_GAUGE_COLORS,
+ stringifyColorValues,
+} from 'src/dashboards/constants/gaugeColors'
import {DASHBOARD_LAYOUT_ROW_HEIGHT} from 'shared/constants'
class GaugeChart extends PureComponent {
@@ -60,7 +63,7 @@ class GaugeChart extends PureComponent {
const {arrayOf, bool, number, shape, string} = PropTypes
GaugeChart.defaultProps = {
- colors: DEFAULT_COLORS,
+ colors: stringifyColorValues(DEFAULT_GAUGE_COLORS),
}
GaugeChart.propTypes = {
diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js
index 101ca72b9..9650a773c 100644
--- a/ui/src/shared/components/LineGraph.js
+++ b/ui/src/shared/components/LineGraph.js
@@ -40,6 +40,7 @@ class LineGraph extends Component {
axes,
cell,
title,
+ colors,
onZoom,
queries,
timeRange,
@@ -83,6 +84,14 @@ class LineGraph extends Component {
? SINGLE_STAT_LINE_COLORS
: overrideLineColors
+ let prefix
+ let suffix
+
+ if (axes) {
+ prefix = axes.y.prefix
+ suffix = axes.y.suffix
+ }
+
return (
{isRefreshing ? : null}
@@ -106,7 +115,14 @@ class LineGraph extends Component {
options={options}
/>
{showSingleStat
- ?
+ ?
: null}
)
@@ -170,6 +186,15 @@ LineGraph.propTypes = {
resizeCoords: shape(),
queries: arrayOf(shape({}).isRequired).isRequired,
data: arrayOf(shape({}).isRequired).isRequired,
+ colors: arrayOf(
+ shape({
+ type: string.isRequired,
+ hex: string.isRequired,
+ id: string.isRequired,
+ name: string.isRequired,
+ value: string.isRequired,
+ }).isRequired
+ ),
}
export default LineGraph
diff --git a/ui/src/shared/components/RefreshingGraph.js b/ui/src/shared/components/RefreshingGraph.js
index 05b7917de..00bca981f 100644
--- a/ui/src/shared/components/RefreshingGraph.js
+++ b/ui/src/shared/components/RefreshingGraph.js
@@ -76,6 +76,7 @@ const RefreshingGraph = ({
return (
color.type === SINGLE_STAT_TEXT)
- if (colors && colors.length > 0) {
- className = 'single-stat single-stat--colored'
- const sortedColors = _.sortBy(colors, color => Number(color.value))
- const nearestCrossedThreshold = sortedColors
- .filter(color => lastValue > color.value)
- .pop()
-
- const colorizeText = _.some(colors, {type: SINGLE_STAT_TEXT})
-
- if (colorizeText) {
- textColor = nearestCrossedThreshold
- ? nearestCrossedThreshold.hex
- : '#292933'
- } else {
- bgColor = nearestCrossedThreshold
- ? nearestCrossedThreshold.hex
- : '#292933'
- textColor = isBackgroundLight(bgColor) ? darkText : lightText
- }
- }
+ const {bgColor, textColor} = generateSingleStatHexs(
+ colors,
+ lineGraph,
+ colorizeText,
+ lastValue
+ )
return (
+ {prefix}
{roundedValue}
{suffix}
+ {lineGraph && }
)
@@ -85,7 +74,9 @@ SingleStat.propTypes = {
value: string.isRequired,
}).isRequired
),
+ prefix: string,
suffix: string,
+ lineGraph: bool,
}
export default SingleStat
diff --git a/ui/src/shared/constants/colorOperations.js b/ui/src/shared/constants/colorOperations.js
index 89258f6bf..b1dbf6ba0 100644
--- a/ui/src/shared/constants/colorOperations.js
+++ b/ui/src/shared/constants/colorOperations.js
@@ -1,3 +1,9 @@
+import _ from 'lodash'
+import {
+ GAUGE_COLORS,
+ SINGLE_STAT_BASE,
+} from 'src/dashboards/constants/gaugeColors'
+
const hexToRgb = hex => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result
@@ -16,9 +22,93 @@ const averageRgbValues = valuesObject => {
const trueNeutralGrey = 128
-export const isBackgroundLight = backgroundColor => {
- const averageBackground = averageRgbValues(hexToRgb(backgroundColor))
- const isLight = averageBackground > trueNeutralGrey
+const getLegibleTextColor = bgColorHex => {
+ const averageBackground = averageRgbValues(hexToRgb(bgColorHex))
+ const isBackgroundLight = averageBackground > trueNeutralGrey
- return isLight
+ const darkText = '#292933'
+ const lightText = '#ffffff'
+
+ return isBackgroundLight ? darkText : lightText
+}
+
+const findNearestCrossedThreshold = (colors, lastValue) => {
+ const sortedColors = _.sortBy(colors, color => Number(color.value))
+ const nearestCrossedThreshold = sortedColors
+ .filter(color => lastValue > color.value)
+ .pop()
+
+ return nearestCrossedThreshold
+}
+
+export const generateSingleStatHexs = (
+ colors,
+ containsLineGraph,
+ colorizeText,
+ lastValue
+) => {
+ const defaultColoring = {bgColor: null, textColor: GAUGE_COLORS[11].hex}
+
+ if (!colors.length || !lastValue) {
+ return defaultColoring
+ }
+
+ // baseColor is expected in all cases
+ const baseColor = colors.find(color => (color.id = SINGLE_STAT_BASE)) || {
+ hex: defaultColoring.textColor,
+ }
+
+ // If the single stat is above a line graph never have a background color
+ if (containsLineGraph) {
+ return baseColor
+ ? {bgColor: null, textColor: baseColor.hex}
+ : defaultColoring
+ }
+
+ // When there is only a base color and it's applied to the text
+ if (colorizeText && colors.length === 1) {
+ return baseColor
+ ? {bgColor: null, textColor: baseColor.hex}
+ : defaultColoring
+ }
+
+ // When there's multiple colors and they're applied to the text
+ if (colorizeText && colors.length > 1) {
+ const nearestCrossedThreshold = findNearestCrossedThreshold(
+ colors,
+ lastValue
+ )
+ const bgColor = null
+ const textColor = nearestCrossedThreshold.hex
+
+ return {bgColor, textColor}
+ }
+
+ // When there is only a base color and it's applued to the background
+ if (colors.length === 1) {
+ const bgColor = baseColor.hex
+ const textColor = getLegibleTextColor(bgColor)
+
+ return {bgColor, textColor}
+ }
+
+ // When there are multiple colors and they're applied to the background
+ if (colors.length > 1) {
+ const nearestCrossedThreshold = findNearestCrossedThreshold(
+ colors,
+ lastValue
+ )
+
+ const bgColor = nearestCrossedThreshold
+ ? nearestCrossedThreshold.hex
+ : baseColor.hex
+ const textColor = getLegibleTextColor(bgColor)
+
+ return {bgColor, textColor}
+ }
+
+ // If all else fails, use safe default
+ const bgColor = null
+ const textColor = baseColor.hex
+ return {bgColor, textColor}
}
diff --git a/ui/src/style/components/ceo-display-options.scss b/ui/src/style/components/ceo-display-options.scss
index 4d5b47322..a381d4bbd 100644
--- a/ui/src/style/components/ceo-display-options.scss
+++ b/ui/src/style/components/ceo-display-options.scss
@@ -242,8 +242,16 @@ button.btn.btn-primary.btn-sm.gauge-controls--add-threshold {
.gauge-controls--input {
flex: 1 0 0;
- margin: 0 4px;
+ margin: 0 0 0 4px;
}
+.gauge-controls--section .color-dropdown {
+ margin-left: 4px;
+}
+.gauge-controls--section .color-dropdown.color-dropdown--stretch {
+ width: auto;
+ flex: 1 0 0;
+}
+
/*
Cell Editor Overlay - Single-Stat Controls
diff --git a/ui/src/style/components/color-dropdown.scss b/ui/src/style/components/color-dropdown.scss
index ab7dd42bc..b892cd0b2 100644
--- a/ui/src/style/components/color-dropdown.scss
+++ b/ui/src/style/components/color-dropdown.scss
@@ -11,6 +11,10 @@ $color-dropdown--circle: 14px;
position: relative;
}
+.color-dropdown.color-dropdown--stretch {
+ width: 100%;
+}
+
.color-dropdown--toggle {
width: 100%;
position: relative;
diff --git a/ui/src/style/components/dygraphs.scss b/ui/src/style/components/dygraphs.scss
index 1e46b2b08..d5fe745f8 100644
--- a/ui/src/style/components/dygraphs.scss
+++ b/ui/src/style/components/dygraphs.scss
@@ -80,6 +80,7 @@
height: calc(100% - 2px);
pointer-events: none;
border-radius: 3px;
+ transition: background-color 0.25s ease, color 0.25s ease;
@include no-user-select();
color: $c-laser;
@@ -92,15 +93,13 @@
height: 100% !important;
}
}
-.single-stat.single-stat--colored {
- transition: background-color 0.25s ease, color 0.25s ease;
-}
.single-stat--value {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
- width: calc(100% - 32px);
+ width: auto;
+ max-width: calc(100% - 32px);
text-align: center;
font-size: 54px;
line-height: 54px;
@@ -115,15 +114,18 @@
}
}
.single-stat--shadow {
- position: relative;
- display: inline-block;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
}
.single-stat--shadow:after {
content: '';
position: absolute;
top: 50%;
left: 50%;
- width: 110%;
+ width: 90%;
height: 0;
transform: translate(-50%,-50%);
box-shadow: fade-out($g2-kevlar, 0.3) 0 0 50px 30px;