({
gaugeColors,
thresholdsListColors,
+ lineColors,
type,
axes,
tableOptions,
diff --git a/ui/src/dashboards/constants/cellEditor.js b/ui/src/dashboards/constants/cellEditor.js
index f22f51f935..7ce2754c11 100644
--- a/ui/src/dashboards/constants/cellEditor.js
+++ b/ui/src/dashboards/constants/cellEditor.js
@@ -1,4 +1,5 @@
import {DEFAULT_TABLE_OPTIONS} from 'src/shared/constants/tableGraph'
+import {stringifyColorValues} from 'src/shared/constants/colorOperations'
export const initializeOptions = cellType => {
switch (cellType) {
@@ -18,3 +19,33 @@ export const AXES_SCALE_OPTIONS = {
export const TOOLTIP_Y_VALUE_FORMAT =
'K/M/B = Thousand / Million / Billion
K/M/G = Kilo / Mega / Giga
'
+
+export const getCellTypeColors = ({
+ cellType,
+ gaugeColors,
+ thresholdsListColors,
+ lineColors,
+}) => {
+ let colors = []
+
+ switch (cellType) {
+ case 'gauge': {
+ colors = stringifyColorValues(gaugeColors)
+ break
+ }
+ case 'single-stat':
+ case 'line-plus-single-stat':
+ case 'table': {
+ colors = stringifyColorValues(thresholdsListColors)
+ break
+ }
+ case 'bar':
+ case 'line':
+ case 'line-stacked':
+ case 'line-stepplot': {
+ colors = stringifyColorValues(lineColors)
+ }
+ }
+
+ return colors
+}
diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js
index 7a9c73ada2..45091183af 100644
--- a/ui/src/dashboards/containers/DashboardPage.js
+++ b/ui/src/dashboards/containers/DashboardPage.js
@@ -37,6 +37,7 @@ import {
import {presentationButtonDispatcher} from 'shared/dispatchers'
import {DASHBOARD_LAYOUT_ROW_HEIGHT} from 'shared/constants'
import {notifyDashboardNotFound} from 'shared/copy/notifications'
+import {colorsStringSchema, colorsNumberSchema} from 'shared/schemas'
const FORMAT_INFLUXQL = 'influxql'
const defaultTimeRange = {
@@ -283,6 +284,7 @@ class DashboardPage extends Component {
showTemplateControlBar,
dashboard,
dashboards,
+ lineColors,
gaugeColors,
autoRefresh,
selectedCell,
@@ -399,6 +401,7 @@ class DashboardPage extends Component {
thresholdsListType={thresholdsListType}
thresholdsListColors={thresholdsListColors}
gaugeColors={gaugeColors}
+ lineColors={lineColors}
/>
) : null}
{
@@ -549,6 +553,7 @@ const mapStateToProps = (state, {params: {dashboardID}}) => {
thresholdsListType,
thresholdsListColors,
gaugeColors,
+ lineColors,
},
} = state
@@ -579,6 +584,7 @@ const mapStateToProps = (state, {params: {dashboardID}}) => {
thresholdsListType,
thresholdsListColors,
gaugeColors,
+ lineColors,
}
}
diff --git a/ui/src/dashboards/reducers/cellEditorOverlay.js b/ui/src/dashboards/reducers/cellEditorOverlay.js
index 4629e89750..d51d2c503c 100644
--- a/ui/src/dashboards/reducers/cellEditorOverlay.js
+++ b/ui/src/dashboards/reducers/cellEditorOverlay.js
@@ -9,6 +9,11 @@ import {
getThresholdsListType,
} from 'shared/constants/thresholds'
+import {
+ DEFAULT_LINE_COLORS,
+ validateLineColors,
+} from 'src/shared/constants/graphColorPalettes'
+
import {initializeOptions} from 'src/dashboards/constants/cellEditor'
export const initialState = {
@@ -16,6 +21,7 @@ export const initialState = {
thresholdsListType: THRESHOLD_TYPE_TEXT,
thresholdsListColors: DEFAULT_THRESHOLDS_LIST_COLORS,
gaugeColors: DEFAULT_GAUGE_COLORS,
+ lineColors: DEFAULT_LINE_COLORS,
}
export default function cellEditorOverlay(state = initialState, action) {
@@ -36,12 +42,15 @@ export default function cellEditorOverlay(state = initialState, action) {
initializeOptions('table')
)
+ const lineColors = validateLineColors(colors)
+
return {
...state,
cell: {...cell, tableOptions},
thresholdsListType,
thresholdsListColors,
gaugeColors,
+ lineColors,
}
}
@@ -101,6 +110,12 @@ export default function cellEditorOverlay(state = initialState, action) {
return {...state, cell}
}
+
+ case 'UPDATE_LINE_COLORS': {
+ const {lineColors} = action.payload
+
+ return {...state, lineColors}
+ }
}
return state
diff --git a/ui/src/data_explorer/components/VisView.js b/ui/src/data_explorer/components/VisView.js
index 2f71af1ad0..b9359d8584 100644
--- a/ui/src/data_explorer/components/VisView.js
+++ b/ui/src/data_explorer/components/VisView.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import Table from './Table'
import RefreshingGraph from 'shared/components/RefreshingGraph'
+import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes'
const VisView = ({
axes,
@@ -37,6 +38,7 @@ const VisView = ({
return (
diff --git a/ui/src/shared/components/ColorDropdown.js b/ui/src/shared/components/ColorDropdown.js
index d5d4d0a1d8..ddfd5da298 100644
--- a/ui/src/shared/components/ColorDropdown.js
+++ b/ui/src/shared/components/ColorDropdown.js
@@ -99,7 +99,7 @@ ColorDropdown.propTypes = {
shape({
hex: string.isRequired,
name: string.isRequired,
- })
+ }).isRequired
).isRequired,
stretchToFit: bool,
disabled: bool,
diff --git a/ui/src/shared/components/ColorScaleDropdown.js b/ui/src/shared/components/ColorScaleDropdown.js
new file mode 100644
index 0000000000..66ca74e55d
--- /dev/null
+++ b/ui/src/shared/components/ColorScaleDropdown.js
@@ -0,0 +1,117 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import uuid from 'uuid'
+import classnames from 'classnames'
+
+import OnClickOutside from 'shared/components/OnClickOutside'
+import FancyScrollbar from 'shared/components/FancyScrollbar'
+
+import {LINE_COLOR_SCALES} from 'src/shared/constants/graphColorPalettes'
+
+class ColorScaleDropdown extends Component {
+ constructor(props) {
+ super(props)
+
+ this.state = {
+ expanded: false,
+ }
+ }
+
+ handleToggleMenu = () => {
+ const {disabled} = this.props
+
+ if (disabled) {
+ return
+ }
+ this.setState({expanded: !this.state.expanded})
+ }
+
+ handleClickOutside = () => {
+ this.setState({expanded: false})
+ }
+
+ handleDropdownClick = colorScale => () => {
+ this.props.onChoose(colorScale)
+ this.setState({expanded: false})
+ }
+
+ generateGradientStyle = colors => ({
+ background: `linear-gradient(to right, ${colors[0].hex} 0%,${
+ colors[1].hex
+ } 50%,${colors[2].hex} 100%)`,
+ })
+
+ render() {
+ const {expanded} = this.state
+ const {selected, disabled, stretchToFit} = this.props
+
+ const dropdownClassNames = classnames('color-dropdown', {
+ open: expanded,
+ 'color-dropdown--stretch': stretchToFit,
+ })
+ const toggleClassNames = classnames(
+ 'btn btn-sm btn-default color-dropdown--toggle',
+ {active: expanded, 'color-dropdown__disabled': disabled}
+ )
+
+ return (
+
+
+
+
{selected[0].name}
+
+
+ {expanded ? (
+
+
+ {LINE_COLOR_SCALES.map(colorScale => (
+
+
+
+ {colorScale.name}
+
+
+ ))}
+
+
+ ) : null}
+
+ )
+ }
+}
+
+const {arrayOf, bool, func, shape, string} = PropTypes
+
+ColorScaleDropdown.propTypes = {
+ selected: arrayOf(
+ shape({
+ type: string.isRequired,
+ hex: string.isRequired,
+ id: string.isRequired,
+ name: string.isRequired,
+ }).isRequired
+ ).isRequired,
+ onChoose: func.isRequired,
+ stretchToFit: bool,
+ disabled: bool,
+}
+
+export default OnClickOutside(ColorScaleDropdown)
diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js
index 59db5ffc0c..4ab51dfe1b 100644
--- a/ui/src/shared/components/Dygraph.js
+++ b/ui/src/shared/components/Dygraph.js
@@ -23,11 +23,17 @@ import {
LABEL_WIDTH,
CHAR_PIXELS,
barPlotter,
- hasherino,
highlightSeriesOpts,
} from 'src/shared/graphs/helpers'
+
+import {
+ DEFAULT_LINE_COLORS,
+ getLineColorsHexes,
+} from 'src/shared/constants/graphColorPalettes'
const {LINEAR, LOG, BASE_10, BASE_2} = AXES_SCALE_OPTIONS
+import {colorsStringSchema} from 'shared/schemas'
+
class Dygraph extends Component {
constructor(props) {
super(props)
@@ -53,7 +59,7 @@ class Dygraph extends Component {
fillGraph,
logscale: y.scale === LOG,
colors: this.getLineColors(),
- series: this.hashColorDygraphSeries(),
+ series: this.colorDygraphSeries(),
plugins: [new Dygraphs.Plugins.Crosshair({direction: 'vertical'})],
axes: {
y: {
@@ -149,7 +155,7 @@ class Dygraph extends Component {
},
},
colors: this.getLineColors(),
- series: this.hashColorDygraphSeries(),
+ series: this.colorDygraphSeries(),
plotter: isBarGraph ? barPlotter : null,
drawCallback: this.annotationsRef.heartbeat,
}
@@ -189,6 +195,33 @@ class Dygraph extends Component {
onZoom(this.formatTimeRange(lower), this.formatTimeRange(upper))
}
+ colorDygraphSeries = () => {
+ const {dygraphSeries, children, colors, overrideLineColors} = this.props
+ const numSeries = Object.keys(dygraphSeries).length
+
+ let lineColors = getLineColorsHexes(colors, numSeries)
+
+ if (React.children && React.children.count(children)) {
+ // If graph is line-plus-single-stat then reserve colors for single stat
+ lineColors = getLineColorsHexes(DEFAULT_LINE_COLORS, numSeries)
+ }
+
+ if (overrideLineColors) {
+ lineColors = getLineColorsHexes(overrideLineColors, numSeries)
+ }
+
+ const coloredDygraphSeries = {}
+
+ for (const seriesName in dygraphSeries) {
+ const series = dygraphSeries[seriesName]
+ const color = lineColors[Object.keys(dygraphSeries).indexOf(seriesName)]
+
+ coloredDygraphSeries[seriesName] = {...series, color}
+ }
+
+ return coloredDygraphSeries
+ }
+
eventToTimestamp = ({pageX: pxBetweenMouseAndPage}) => {
const {left: pxBetweenGraphAndPage} = this.graphRef.getBoundingClientRect()
const graphXCoordinate = pxBetweenMouseAndPage - pxBetweenGraphAndPage
@@ -213,21 +246,6 @@ class Dygraph extends Component {
this.setState({isHoveringThisGraph: false})
}
- hashColorDygraphSeries = () => {
- const {dygraphSeries} = this.props
- const colors = this.getLineColors()
- const hashColorDygraphSeries = {}
-
- for (const seriesName in dygraphSeries) {
- const series = dygraphSeries[seriesName]
- const hashIndex = hasherino(seriesName, colors.length)
- const color = colors[hashIndex]
- hashColorDygraphSeries[seriesName] = {...series, color}
- }
-
- return hashColorDygraphSeries
- }
-
handleHideLegend = e => {
const {top, bottom, left, right} = this.graphRef.getBoundingClientRect()
@@ -363,7 +381,7 @@ class Dygraph extends Component {
/>
{staticLegend && (
({
diff --git a/ui/src/shared/components/Gauge.js b/ui/src/shared/components/Gauge.js
index 52ea1614eb..4d1e3b5692 100644
--- a/ui/src/shared/components/Gauge.js
+++ b/ui/src/shared/components/Gauge.js
@@ -10,6 +10,8 @@ import {
MIN_THRESHOLDS,
} from 'shared/constants/thresholds'
+import {colorsStringSchema} from 'shared/schemas'
+
class Gauge extends Component {
constructor(props) {
super(props)
@@ -325,21 +327,13 @@ class Gauge extends Component {
}
}
-const {arrayOf, number, shape, string} = PropTypes
+const {number, string} = PropTypes
Gauge.propTypes = {
width: string.isRequired,
height: string.isRequired,
gaugePosition: number.isRequired,
- colors: arrayOf(
- shape({
- type: string.isRequired,
- hex: string.isRequired,
- id: string.isRequired,
- name: string.isRequired,
- value: string.isRequired,
- }).isRequired
- ).isRequired,
+ colors: colorsStringSchema.isRequired,
prefix: string.isRequired,
suffix: string.isRequired,
}
diff --git a/ui/src/shared/components/GaugeChart.js b/ui/src/shared/components/GaugeChart.js
index 0516d9c11d..cad6d44280 100644
--- a/ui/src/shared/components/GaugeChart.js
+++ b/ui/src/shared/components/GaugeChart.js
@@ -7,6 +7,8 @@ import {DEFAULT_GAUGE_COLORS} from 'src/shared/constants/thresholds'
import {stringifyColorValues} from 'src/shared/constants/colorOperations'
import {DASHBOARD_LAYOUT_ROW_HEIGHT} from 'src/shared/constants'
+import {colorsStringSchema} from 'shared/schemas'
+
class GaugeChart extends PureComponent {
render() {
const {
@@ -82,15 +84,7 @@ GaugeChart.propTypes = {
cellHeight: number,
resizerTopHeight: number,
resizeCoords: shape(),
- colors: arrayOf(
- shape({
- type: string.isRequired,
- hex: string.isRequired,
- id: string.isRequired,
- name: string.isRequired,
- value: string.isRequired,
- }).isRequired
- ),
+ colors: colorsStringSchema,
prefix: string.isRequired,
suffix: string.isRequired,
}
diff --git a/ui/src/shared/components/Layout.js b/ui/src/shared/components/Layout.js
index 18ade5ada1..654593492a 100644
--- a/ui/src/shared/components/Layout.js
+++ b/ui/src/shared/components/Layout.js
@@ -8,6 +8,8 @@ import {IS_STATIC_LEGEND} from 'src/shared/constants'
import _ from 'lodash'
+import {colorsStringSchema} from 'shared/schemas'
+
const getSource = (cell, source, sources, defaultSource) => {
const s = _.get(cell, ['queries', '0', 'source'], null)
if (!s) {
@@ -134,15 +136,7 @@ const propTypes = {
i: string.isRequired,
name: string.isRequired,
type: string.isRequired,
- colors: arrayOf(
- shape({
- type: string.isRequired,
- hex: string.isRequired,
- id: string.isRequired,
- name: string.isRequired,
- value: string.isRequired,
- }).isRequired
- ),
+ colors: colorsStringSchema,
}).isRequired,
templates: arrayOf(shape()),
host: string,
diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js
index 25905e3173..ffab2cfc2d 100644
--- a/ui/src/shared/components/LineGraph.js
+++ b/ui/src/shared/components/LineGraph.js
@@ -6,7 +6,7 @@ import shallowCompare from 'react-addons-shallow-compare'
import SingleStat from 'src/shared/components/SingleStat'
import timeSeriesToDygraph from 'utils/timeSeriesTransformers'
-import {SINGLE_STAT_LINE_COLORS} from 'src/shared/graphs/helpers'
+import {colorsStringSchema} from 'shared/schemas'
class LineGraph extends Component {
constructor(props) {
@@ -85,10 +85,6 @@ class LineGraph extends Component {
connectSeparatedPoints: true,
}
- const lineColors = showSingleStat
- ? SINGLE_STAT_LINE_COLORS
- : overrideLineColors
-
const containerStyle = {
width: 'calc(100% - 32px)',
height: 'calc(100% - 16px)',
@@ -118,7 +114,8 @@ class LineGraph extends Component {
resizeCoords={resizeCoords}
dygraphSeries={dygraphSeries}
setResolution={setResolution}
- overrideLineColors={lineColors}
+ colors={colors}
+ overrideLineColors={overrideLineColors}
containerStyle={containerStyle}
staticLegend={staticLegend}
isGraphFilled={showSingleStat ? false : isGraphFilled}
@@ -201,15 +198,7 @@ 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
- ),
+ colors: colorsStringSchema,
}
export default LineGraph
diff --git a/ui/src/shared/components/LineGraphColorSelector.js b/ui/src/shared/components/LineGraphColorSelector.js
new file mode 100644
index 0000000000..50ca3c838d
--- /dev/null
+++ b/ui/src/shared/components/LineGraphColorSelector.js
@@ -0,0 +1,52 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import {connect} from 'react-redux'
+import {bindActionCreators} from 'redux'
+
+import ColorScaleDropdown from 'shared/components/ColorScaleDropdown'
+
+import {updateLineColors} from 'src/dashboards/actions/cellEditorOverlay'
+import {colorsStringSchema} from 'shared/schemas'
+
+class LineGraphColorSelector extends Component {
+ handleSelectColors = colorScale => {
+ const {handleUpdateLineColors} = this.props
+ const {colors} = colorScale
+
+ handleUpdateLineColors(colors)
+ }
+
+ render() {
+ const {lineColors} = this.props
+
+ return (
+
+
+
+
+ )
+ }
+}
+
+const {func} = PropTypes
+
+LineGraphColorSelector.propTypes = {
+ lineColors: colorsStringSchema.isRequired,
+ handleUpdateLineColors: func.isRequired,
+}
+
+const mapStateToProps = ({cellEditorOverlay: {lineColors}}) => ({
+ lineColors,
+})
+
+const mapDispatchToProps = dispatch => ({
+ handleUpdateLineColors: bindActionCreators(updateLineColors, dispatch),
+})
+
+export default connect(mapStateToProps, mapDispatchToProps)(
+ LineGraphColorSelector
+)
diff --git a/ui/src/shared/components/RefreshingGraph.js b/ui/src/shared/components/RefreshingGraph.js
index 3468329eea..c3597b618d 100644
--- a/ui/src/shared/components/RefreshingGraph.js
+++ b/ui/src/shared/components/RefreshingGraph.js
@@ -9,6 +9,8 @@ import SingleStat from 'shared/components/SingleStat'
import GaugeChart from 'shared/components/GaugeChart'
import TableGraph from 'shared/components/TableGraph'
+import {colorsStringSchema} from 'shared/schemas'
+
const RefreshingLineGraph = AutoRefresh(LineGraph)
const RefreshingSingleStat = AutoRefresh(SingleStat)
const RefreshingGaugeChart = AutoRefresh(GaugeChart)
@@ -154,15 +156,7 @@ RefreshingGraph.propTypes = {
onZoom: func,
resizeCoords: shape(),
grabDataForDownload: func,
- colors: arrayOf(
- shape({
- type: string.isRequired,
- hex: string.isRequired,
- id: string.isRequired,
- name: string.isRequired,
- value: string.isRequired,
- }).isRequired
- ),
+ colors: colorsStringSchema,
cellID: string,
inView: bool,
tableOptions: shape({}),
diff --git a/ui/src/shared/components/SingleStat.js b/ui/src/shared/components/SingleStat.js
index dc91e872db..99e17e12ef 100644
--- a/ui/src/shared/components/SingleStat.js
+++ b/ui/src/shared/components/SingleStat.js
@@ -6,6 +6,7 @@ import lastValues from 'shared/parsing/lastValues'
import {SMALL_CELL_HEIGHT} from 'shared/graphs/helpers'
import {DYGRAPH_CONTAINER_V_MARGIN} from 'shared/constants'
import {generateThresholdsListHexs} from 'shared/constants/colorOperations'
+import {colorsStringSchema} from 'shared/schemas'
class SingleStat extends PureComponent {
render() {
@@ -78,15 +79,7 @@ SingleStat.propTypes = {
data: arrayOf(shape()).isRequired,
isFetchingInitially: bool,
cellHeight: number,
- colors: arrayOf(
- shape({
- type: string.isRequired,
- hex: string.isRequired,
- id: string.isRequired,
- name: string.isRequired,
- value: string.isRequired,
- }).isRequired
- ),
+ colors: colorsStringSchema,
prefix: string,
suffix: string,
lineGraph: bool,
diff --git a/ui/src/shared/components/StaticLegend.js b/ui/src/shared/components/StaticLegend.js
index f79c6cf115..7de93f177a 100644
--- a/ui/src/shared/components/StaticLegend.js
+++ b/ui/src/shared/components/StaticLegend.js
@@ -86,10 +86,6 @@ class StaticLegend extends Component {
key={uuid.v4()}
onMouseDown={this.handleClick(i)}
>
-
{removeMeasurement(v)}
))}
diff --git a/ui/src/shared/components/TableGraph.js b/ui/src/shared/components/TableGraph.js
index 60edc76fc7..3cb2623a2e 100644
--- a/ui/src/shared/components/TableGraph.js
+++ b/ui/src/shared/components/TableGraph.js
@@ -27,6 +27,7 @@ import {
} from 'src/shared/constants/tableGraph'
import {generateThresholdsListHexs} from 'shared/constants/colorOperations'
+import {colorsStringSchema} from 'shared/schemas'
class TableGraph extends Component {
constructor(props) {
@@ -432,15 +433,7 @@ TableGraph.propTypes = {
}),
hoverTime: string,
onSetHoverTime: func,
- colors: arrayOf(
- shape({
- type: string.isRequired,
- hex: string.isRequired,
- id: string.isRequired,
- name: string.isRequired,
- value: string.isRequired,
- }).isRequired
- ),
+ colors: colorsStringSchema,
setDataLabels: func,
}
diff --git a/ui/src/shared/components/ThresholdsList.js b/ui/src/shared/components/ThresholdsList.js
index 42ba1f9821..669e23fd07 100644
--- a/ui/src/shared/components/ThresholdsList.js
+++ b/ui/src/shared/components/ThresholdsList.js
@@ -10,6 +10,7 @@ import Threshold from 'src/dashboards/components/Threshold'
import ColorDropdown from 'shared/components/ColorDropdown'
import {updateThresholdsListColors} from 'src/dashboards/actions/cellEditorOverlay'
+import {colorsNumberSchema} from 'shared/schemas'
import {
THRESHOLD_COLORS,
@@ -166,22 +167,14 @@ class ThresholdsList extends Component {
)
}
}
-const {arrayOf, bool, func, number, shape, string} = PropTypes
+const {bool, func, string} = PropTypes
ThresholdsList.defaultProps = {
showListHeading: false,
}
ThresholdsList.propTypes = {
thresholdsListType: string.isRequired,
- thresholdsListColors: arrayOf(
- shape({
- type: string.isRequired,
- hex: string.isRequired,
- id: string.isRequired,
- name: string.isRequired,
- value: number.isRequired,
- }).isRequired
- ),
+ thresholdsListColors: colorsNumberSchema.isRequired,
handleUpdateThresholdsListColors: func.isRequired,
onResetFocus: func.isRequired,
showListHeading: bool,
diff --git a/ui/src/shared/constants/colorOperations.js b/ui/src/shared/constants/colorOperations.js
index 275289703e..3c745c904f 100644
--- a/ui/src/shared/constants/colorOperations.js
+++ b/ui/src/shared/constants/colorOperations.js
@@ -1,36 +1,21 @@
import _ from 'lodash'
+import chroma from 'chroma-js'
+
import {
THRESHOLD_COLORS,
THRESHOLD_TYPE_BASE,
THRESHOLD_TYPE_TEXT,
} from 'shared/constants/thresholds'
-const hexToRgb = hex => {
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
- return result
- ? {
- r: parseInt(result[1], 16),
- g: parseInt(result[2], 16),
- b: parseInt(result[3], 16),
- }
- : null
-}
-
-const averageRgbValues = valuesObject => {
- const {r, g, b} = valuesObject
- return (r + g + b) / 3
-}
-
-const trueNeutralGrey = 128
+const luminanceThreshold = 0.5
const getLegibleTextColor = bgColorHex => {
- const averageBackground = averageRgbValues(hexToRgb(bgColorHex))
- const isBackgroundLight = averageBackground > trueNeutralGrey
-
const darkText = '#292933'
const lightText = '#ffffff'
- return isBackgroundLight ? darkText : lightText
+ return chroma(bgColorHex).luminance() < luminanceThreshold
+ ? darkText
+ : lightText
}
const findNearestCrossedThreshold = (colors, lastValue) => {
diff --git a/ui/src/shared/constants/graphColorPalettes.js b/ui/src/shared/constants/graphColorPalettes.js
new file mode 100644
index 0000000000..3b3768b4cc
--- /dev/null
+++ b/ui/src/shared/constants/graphColorPalettes.js
@@ -0,0 +1,228 @@
+import chroma from 'chroma-js'
+
+const COLOR_TYPE_SCALE = 'scale'
+
+// Color Palettes
+export const LINE_COLORS_A = [
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#31C0F6',
+ id: '0',
+ name: 'Nineteen Eighty Four',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#A500A5',
+ id: '0',
+ name: 'Nineteen Eighty Four',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#FF7E27',
+ id: '0',
+ name: 'Nineteen Eighty Four',
+ value: '0',
+ },
+]
+
+export const LINE_COLORS_B = [
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#74D495',
+ id: '1',
+ name: 'Atlantis',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#3F3FBA',
+ id: '1',
+ name: 'Atlantis',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#FF4D9E',
+ id: '1',
+ name: 'Atlantis',
+ value: '0',
+ },
+]
+
+export const LINE_COLORS_C = [
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#8F8AF4',
+ id: '1',
+ name: 'Do Androids Dream of Electric Sheep?',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#A51414',
+ id: '1',
+ name: 'Do Androids Dream of Electric Sheep?',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#F4CF31',
+ id: '1',
+ name: 'Do Androids Dream of Electric Sheep?',
+ value: '0',
+ },
+]
+
+export const LINE_COLORS_D = [
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#FD7A5D',
+ id: '1',
+ name: 'Delorean',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#5F1CF2',
+ id: '1',
+ name: 'Delorean',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#4CE09A',
+ id: '1',
+ name: 'Delorean',
+ value: '0',
+ },
+]
+
+export const LINE_COLORS_E = [
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#FDC44F',
+ id: '1',
+ name: 'Cthulhu',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#007C76',
+ id: '1',
+ name: 'Cthulhu',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#8983FF',
+ id: '1',
+ name: 'Cthulhu',
+ value: '0',
+ },
+]
+
+export const LINE_COLORS_F = [
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#DA6FF1',
+ id: '1',
+ name: 'Ectoplasm',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#00717A',
+ id: '1',
+ name: 'Ectoplasm',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#ACFF76',
+ id: '1',
+ name: 'Ectoplasm',
+ value: '0',
+ },
+]
+
+export const LINE_COLORS_G = [
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#F6F6F8',
+ id: '1',
+ name: 'T-Max 400 Film',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#A4A8B6',
+ id: '1',
+ name: 'T-Max 400 Film',
+ value: '0',
+ },
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#545667',
+ id: '1',
+ name: 'T-Max 400 Film',
+ value: '0',
+ },
+]
+
+export const LINE_COLORS_RULE_GRAPH = [
+ {
+ type: COLOR_TYPE_SCALE,
+ hex: '#4ED8A0',
+ id: '1',
+ name: 'Rainforest',
+ value: '0',
+ },
+]
+
+export const DEFAULT_LINE_COLORS = LINE_COLORS_A
+
+export const LINE_COLOR_SCALES = [
+ LINE_COLORS_A,
+ LINE_COLORS_B,
+ LINE_COLORS_C,
+ LINE_COLORS_D,
+ LINE_COLORS_E,
+ LINE_COLORS_F,
+ LINE_COLORS_G,
+].map(colorScale => {
+ const name = colorScale[0].name
+ const colors = colorScale
+ const id = colorScale[0].id
+
+ return {name, colors, id}
+})
+
+export const validateLineColors = colors => {
+ if (!colors || colors.length === 0) {
+ return DEFAULT_LINE_COLORS
+ }
+
+ const testColorsTypes =
+ colors.filter(color => color.type === COLOR_TYPE_SCALE).length ===
+ colors.length
+
+ return testColorsTypes ? colors : DEFAULT_LINE_COLORS
+}
+
+export const getLineColorsHexes = (colors, numSeries) => {
+ const validatedColors = validateLineColors(colors) // ensures safe defaults
+ const colorsHexArray = validatedColors.map(color => color.hex)
+
+ if (numSeries === 1 || numSeries === 0) {
+ return [colorsHexArray[0]]
+ }
+ if (numSeries === 2) {
+ return [colorsHexArray[0], colorsHexArray[1]]
+ }
+ return chroma
+ .scale(colorsHexArray)
+ .mode('lch')
+ .colors(numSeries)
+}
diff --git a/ui/src/shared/schemas.js b/ui/src/shared/schemas.js
index 35df59a72b..5e16535433 100644
--- a/ui/src/shared/schemas.js
+++ b/ui/src/shared/schemas.js
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'
-const {shape, string} = PropTypes
+const {arrayOf, number, shape, string} = PropTypes
export const annotation = shape({
id: string.isRequired,
@@ -9,3 +9,23 @@ export const annotation = shape({
text: string.isRequired,
type: string.isRequired,
})
+
+export const colorsStringSchema = arrayOf(
+ shape({
+ type: string.isRequired,
+ hex: string.isRequired,
+ id: string.isRequired,
+ name: string.isRequired,
+ value: string.isRequired,
+ }).isRequired
+)
+
+export const colorsNumberSchema = arrayOf(
+ shape({
+ type: string.isRequired,
+ hex: string.isRequired,
+ id: string.isRequired,
+ name: string.isRequired,
+ value: number.isRequired,
+ }).isRequired
+)
diff --git a/ui/src/status/fixtures.js b/ui/src/status/fixtures.js
index d366801897..3e96f036f0 100644
--- a/ui/src/status/fixtures.js
+++ b/ui/src/status/fixtures.js
@@ -1,3 +1,5 @@
+import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes'
+
export const fixtureStatusPageCells = [
{
i: 'alerts-bar-graph',
@@ -8,6 +10,7 @@ export const fixtureStatusPageCells = [
h: 4,
legend: {},
name: 'Alert Events per Day – Last 30 Days',
+ colors: DEFAULT_LINE_COLORS,
queries: [
{
query:
diff --git a/ui/src/style/components/color-dropdown.scss b/ui/src/style/components/color-dropdown.scss
index b892cd0b22..99f6901f7b 100644
--- a/ui/src/style/components/color-dropdown.scss
+++ b/ui/src/style/components/color-dropdown.scss
@@ -4,6 +4,10 @@
*/
$color-dropdown--circle: 14px;
+$color-dropdown--bar: 104px;
+$color-dropdown--bar-height: 10px;
+$color-dropdown--left-padding: 11px;
+$color-dropdown--name-padding: 20px;
.color-dropdown {
width: 140px;
@@ -31,11 +35,11 @@ $color-dropdown--circle: 14px;
position: absolute;
top: 30px;
left: 0;
- z-index: 2;
+ z-index: 5;
width: 100%;
border-radius: 4px;
box-shadow: 0 2px 5px 0.6px fade-out($g0-obsidian, 0.7);
- @include gradient-h($g0-obsidian,$g2-kevlar);
+ @include gradient-h($g0-obsidian, $g2-kevlar);
}
.color-dropdown--item {
@include no-user-select();
@@ -43,9 +47,7 @@ $color-dropdown--circle: 14px;
height: 28px;
position: relative;
color: $g11-sidewalk;
- transition:
- color 0.25s ease,
- background-color 0.25s ease;
+ transition: color 0.25s ease, background-color 0.25s ease;
&:hover {
background-color: $g4-onyx;
@@ -67,6 +69,7 @@ $color-dropdown--circle: 14px;
}
}
.color-dropdown--swatch,
+.color-dropdown--swatches,
.color-dropdown--name {
position: absolute;
top: 50%;
@@ -76,21 +79,35 @@ $color-dropdown--circle: 14px;
width: $color-dropdown--circle;
height: $color-dropdown--circle;
border-radius: 50%;
- left: 11px;
+ left: $color-dropdown--left-padding;
+}
+.color-dropdown--swatches {
+ width: $color-dropdown--bar;
+ height: $color-dropdown--bar-height;
+ border-radius: $color-dropdown--bar-height / 2;
+ left: $color-dropdown--left-padding;
}
.color-dropdown--name {
text-align: left;
- right: 11px;
- left: 34px;
+ right: $color-dropdown--name-padding;
+ left: $color-dropdown--circle + $color-dropdown--name-padding;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
font-weight: 600;
text-transform: capitalize;
+
+ .color-dropdown--swatches + & {
+ left: $color-dropdown--bar + $color-dropdown--name-padding;
+ }
}
-.color-dropdown .color-dropdown--menu .fancy-scroll--container .fancy-scroll--track-v .fancy-scroll--thumb-v {
- @include gradient-v($g9-mountain,$g7-graphite);
+.color-dropdown
+ .color-dropdown--menu
+ .fancy-scroll--container
+ .fancy-scroll--track-v
+ .fancy-scroll--thumb-v {
+ @include gradient-v($g9-mountain, $g7-graphite);
}
.color-dropdown--toggle.color-dropdown__disabled {
color: $g7-graphite;
diff --git a/ui/src/style/components/static-legend.scss b/ui/src/style/components/static-legend.scss
index a9600a11a5..252620299d 100644
--- a/ui/src/style/components/static-legend.scss
+++ b/ui/src/style/components/static-legend.scss
@@ -16,17 +16,9 @@
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;
+ @include custom-scrollbar($g3-castle, $g6-smoke);
}
+
.static-legend--item,
.static-legend--single {
height: 22px;
@@ -43,8 +35,7 @@
.static-legend--item {
transition: background-color 0.25s ease, color 0.25s ease;
- span,
- .static-legend--dot {
+ span {
opacity: 0.8;
transition: opacity 0.25s ease;
}
@@ -53,8 +44,7 @@
cursor: pointer;
background-color: $g6-smoke;
- span,
- .static-legend--dot {
+ span {
opacity: 1;
}
}
@@ -62,16 +52,14 @@
background-color: $g1-raven;
font-style: italic;
- span,
- .static-legend--dot {
+ span {
opacity: 0.35;
}
&:hover {
background-color: $g2-kevlar;
- span,
- .static-legend--dot {
+ span {
opacity: 0.65;
}
}
diff --git a/ui/test/dashboards/reducers/cellEditorOverlay.test.js b/ui/test/dashboards/reducers/cellEditorOverlay.test.js
index 2d29ecf67b..eb11c734e0 100644
--- a/ui/test/dashboards/reducers/cellEditorOverlay.test.js
+++ b/ui/test/dashboards/reducers/cellEditorOverlay.test.js
@@ -8,6 +8,7 @@ import {
updateThresholdsListColors,
updateThresholdsListType,
updateGaugeColors,
+ updateLineColors,
updateAxes,
} from 'src/dashboards/actions/cellEditorOverlay'
import {DEFAULT_TABLE_OPTIONS} from 'src/shared/constants/tableGraph'
@@ -17,6 +18,7 @@ import {
validateThresholdsListColors,
getThresholdsListType,
} from 'shared/constants/thresholds'
+import {validateLineColors} from 'src/shared/constants/graphColorPalettes'
const defaultCellType = 'line'
const defaultCellName = 'defaultCell'
@@ -45,6 +47,7 @@ const defaultThresholdsListColors = validateThresholdsListColors(
defaultThresholdsListType
)
const defaultGaugeColors = validateGaugeColors(defaultCell.colors)
+const defaultLineColors = validateLineColors(defaultCell.colors)
describe('Dashboards.Reducers.cellEditorOverlay', () => {
it('should show cell editor overlay', () => {
@@ -117,4 +120,11 @@ describe('Dashboards.Reducers.cellEditorOverlay', () => {
expect(actual.cell.axes).toBe(expected)
})
+
+ it('should update the cell line graph colors', () => {
+ const actual = reducer(initialState, updateLineColors(defaultLineColors))
+ const expected = defaultLineColors
+
+ expect(actual.lineColors).toBe(expected)
+ })
})
diff --git a/ui/yarn.lock b/ui/yarn.lock
index 0c053dee44..b8c44de637 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -1686,6 +1686,10 @@ chownr@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
+chroma-js@^1.3.6:
+ version "1.3.6"
+ resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-1.3.6.tgz#22dd7220ef6b55dcfcb8ef92982baaf55dced45d"
+
ci-info@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4"