diff --git a/ui/src/dashboards/actions/cellEditorOverlay.js b/ui/src/dashboards/actions/cellEditorOverlay.js index 34b4a5354e..f9efe0de08 100644 --- a/ui/src/dashboards/actions/cellEditorOverlay.js +++ b/ui/src/dashboards/actions/cellEditorOverlay.js @@ -57,3 +57,10 @@ export const updateTableOptions = tableOptions => ({ tableOptions, }, }) + +export const updateLineColors = lineColors => ({ + type: 'UPDATE_LINE_COLORS', + payload: { + lineColors, + }, +}) diff --git a/ui/src/dashboards/components/AxesOptions.js b/ui/src/dashboards/components/AxesOptions.js index c0e5019a7d..9b04de4659 100644 --- a/ui/src/dashboards/components/AxesOptions.js +++ b/ui/src/dashboards/components/AxesOptions.js @@ -7,6 +7,7 @@ import OptIn from 'shared/components/OptIn' import Input from 'src/dashboards/components/DisplayOptionsInput' import {Tabber, Tab} from 'src/dashboards/components/Tabber' import FancyScrollbar from 'shared/components/FancyScrollbar' +import LineGraphColorSelector from 'src/shared/components/LineGraphColorSelector' import { AXES_SCALE_OPTIONS, @@ -102,6 +103,7 @@ class AxesOptions extends Component { type="text" /> +
{ const {queriesWorkingDraft, staticLegend} = this.state - const {cell, thresholdsListColors, gaugeColors} = this.props + const {cell, thresholdsListColors, gaugeColors, lineColors} = this.props const queries = queriesWorkingDraft.map(q => { const timeRange = q.range || {upper: null, lower: ':dashboardTime:'} @@ -133,6 +133,12 @@ class CellEditorOverlay extends Component { colors = stringifyColorValues(thresholdsListColors) break } + case 'bar': + case 'line': + case 'line-stacked': + case 'line-stepplot': { + colors = stringifyColorValues(lineColors) + } } this.props.onSave({ @@ -392,6 +398,7 @@ CellEditorOverlay.propTypes = { thresholdsListType: string.isRequired, thresholdsListColors: arrayOf(shape({}).isRequired).isRequired, gaugeColors: arrayOf(shape({}).isRequired).isRequired, + lineColors: arrayOf(shape({}).isRequired).isRequired, } CEOBottom.propTypes = { diff --git a/ui/src/dashboards/components/Visualization.js b/ui/src/dashboards/components/Visualization.js index d84a76ed06..b9131af830 100644 --- a/ui/src/dashboards/components/Visualization.js +++ b/ui/src/dashboards/components/Visualization.js @@ -14,6 +14,7 @@ const DashVisualization = ( type, templates, timeRange, + lineColors, autoRefresh, gaugeColors, queryConfigs, @@ -26,14 +27,32 @@ const DashVisualization = ( }, {source: {links: {proxy}}} ) => { - const colors = type === 'gauge' ? gaugeColors : thresholdsListColors + let colors = [] + switch (type) { + 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 (
({ gaugeColors, thresholdsListColors, + lineColors, type, axes, tableOptions, diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 29516e202d..5df566e8ab 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -267,6 +267,7 @@ class DashboardPage extends Component { showTemplateControlBar, dashboard, dashboards, + lineColors, gaugeColors, autoRefresh, selectedCell, @@ -383,6 +384,7 @@ class DashboardPage extends Component { thresholdsListType={thresholdsListType} thresholdsListColors={thresholdsListColors} gaugeColors={gaugeColors} + lineColors={lineColors} /> ) : null} { @@ -532,6 +535,7 @@ const mapStateToProps = (state, {params: {dashboardID}}) => { thresholdsListType, thresholdsListColors, gaugeColors, + lineColors, }, } = state @@ -562,6 +566,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/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 7fe6e70399..e5a166093d 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types' import {connect} from 'react-redux' import shallowCompare from 'react-addons-shallow-compare' import _ from 'lodash' +import chroma from 'chroma-js' import NanoDate from 'nano-date' import Dygraphs from 'src/external/dygraph' @@ -26,7 +27,11 @@ import { highlightSeriesOpts, } from 'src/shared/graphs/helpers' -import {generateColorScale} from 'src/shared/constants/graphColorPalettes' +import { + DEFAULT_LINE_COLORS, + validateLineColors, + transformColorsForChroma, +} from 'src/shared/constants/graphColorPalettes' const {LINEAR, LOG, BASE_10, BASE_2} = AXES_SCALE_OPTIONS class Dygraph extends Component { @@ -191,15 +196,35 @@ class Dygraph extends Component { } colorDygraphSeries = () => { - const {dygraphSeries} = this.props + const {dygraphSeries, children, colors, overrideLineColors} = this.props const numSeries = Object.keys(dygraphSeries).length - const colors = generateColorScale(numSeries) + const validatedLineColors = validateLineColors(colors) // ensures safe defaults + + let lineColors = chroma + .scale(transformColorsForChroma(validatedLineColors)) + .mode('lch') + .colors(numSeries) + + if (React.children && React.children.count(children)) { + // If graph is line-plus-single-stat then reserve colors for single stat + lineColors = chroma + .scale(transformColorsForChroma(DEFAULT_LINE_COLORS)) + .mode('lch') + .colors(numSeries) + } + + if (overrideLineColors && overrideLineColors.length > 0) { + lineColors = chroma + .scale(overrideLineColors) + .mode('lch') + .colors(numSeries) + } const coloredDygraphSeries = {} for (const seriesName in dygraphSeries) { const series = dygraphSeries[seriesName] - const color = colors[Object.keys(dygraphSeries).indexOf(seriesName)] + const color = lineColors[Object.keys(dygraphSeries).indexOf(seriesName)] coloredDygraphSeries[seriesName] = {...series, color} } @@ -423,7 +448,7 @@ Dygraph.propTypes = { isGraphFilled: bool, isBarGraph: bool, staticLegend: bool, - overrideLineColors: array, + overrideLineColors: arrayOf(string.isRequired), dygraphSeries: shape({}).isRequired, ruleValues: shape({ operator: string, @@ -440,6 +465,15 @@ Dygraph.propTypes = { onZoom: func, mode: string, children: node, + colors: arrayOf( + shape({ + type: string.isRequired, + hex: string.isRequired, + id: string.isRequired, + name: string.isRequired, + value: string.isRequired, + }).isRequired + ), } const mapStateToProps = ({annotations: {mode}}) => ({ diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index a7cabea3a1..305602b525 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -6,8 +6,6 @@ import shallowCompare from 'react-addons-shallow-compare' import SingleStat from 'src/shared/components/SingleStat' import timeSeriesToDygraph from 'utils/timeSeriesToDygraph' -import {SINGLE_STAT_LINE_COLORS} from 'src/shared/graphs/helpers' - class LineGraph extends Component { constructor(props) { super(props) @@ -84,10 +82,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)', @@ -117,7 +111,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} diff --git a/ui/src/shared/components/LineGraphColorSelector.js b/ui/src/shared/components/LineGraphColorSelector.js new file mode 100644 index 0000000000..a8bfc8e06b --- /dev/null +++ b/ui/src/shared/components/LineGraphColorSelector.js @@ -0,0 +1,59 @@ +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' + +class LineGraphColorSelector extends Component { + handleSelectColors = colorScale => { + const {handleUpdateLineColors} = this.props + const {colors} = colorScale + + handleUpdateLineColors(colors) + } + + render() { + const {lineColors} = this.props + + return ( +
+ + +
+ ) + } +} + +const {arrayOf, func, shape, string, number} = PropTypes + +LineGraphColorSelector.propTypes = { + lineColors: arrayOf( + shape({ + type: string.isRequired, + hex: string.isRequired, + id: string.isRequired, + name: string.isRequired, + value: number.isRequired, + }).isRequired + ).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/constants/graphColorPalettes.js b/ui/src/shared/constants/graphColorPalettes.js index ba8773c829..c7d6ed9f5c 100644 --- a/ui/src/shared/constants/graphColorPalettes.js +++ b/ui/src/shared/constants/graphColorPalettes.js @@ -1,24 +1,23 @@ -import _ from 'lodash' -import chroma from 'chroma-js' +const COLOR_TYPE_SCALE = 'scale' // Color Palettes export const LINE_COLORS_A = [ { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#31C0F6', id: '0', name: 'Nineteen Eighty Four', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#A500A5', id: '0', name: 'Nineteen Eighty Four', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#FF7E27', id: '0', name: 'Nineteen Eighty Four', @@ -28,21 +27,21 @@ export const LINE_COLORS_A = [ export const LINE_COLORS_B = [ { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#74D495', id: '1', name: 'Atlantis', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#3F3FBA', id: '1', name: 'Atlantis', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#EA5994', id: '1', name: 'Atlantis', @@ -52,124 +51,126 @@ export const LINE_COLORS_B = [ export const LINE_COLORS_C = [ { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#8F8AF4', id: '1', - name: 'Glarbh', + name: 'Do Androids Dream of Electric Sheep?', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#A51414', id: '1', - name: 'Glarbh', + name: 'Do Androids Dream of Electric Sheep?', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#F4CF31', id: '1', - name: 'Glarbh', + name: 'Do Androids Dream of Electric Sheep?', value: 0, }, ] export const LINE_COLORS_D = [ { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#FD7A5D', id: '1', - name: 'Spoot', + name: 'Delorean', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#5F1CF2', id: '1', - name: 'Spoot', + name: 'Delorean', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#4CE09A', id: '1', - name: 'Spoot', + name: 'Delorean', value: 0, }, ] export const LINE_COLORS_E = [ { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#FDC44F', id: '1', - name: 'Swump', + name: 'Cthulhu', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#007C76', id: '1', - name: 'Swump', + name: 'Cthulhu', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#8983FF', id: '1', - name: 'Swump', + name: 'Cthulhu', value: 0, }, ] export const LINE_COLORS_F = [ { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#DA6FF1', id: '1', - name: 'Splort', + name: 'Ectoplasm', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#00717A', id: '1', - name: 'Splort', + name: 'Ectoplasm', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#05B7E0', id: '1', - name: 'Splort', + name: 'Ectoplasm', value: 0, }, ] export const LINE_COLORS_G = [ { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#F6F6F8', id: '1', - name: 'OldTimey', + name: 'T-Max 400 Film', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#A4A8B6', id: '1', - name: 'OldTimey', + name: 'T-Max 400 Film', value: 0, }, { - type: 'scale', + type: COLOR_TYPE_SCALE, hex: '#545667', id: '1', - name: 'OldTimey', + name: 'T-Max 400 Film', value: 0, }, ] +export const DEFAULT_LINE_COLORS = LINE_COLORS_A + export const LINE_COLOR_SCALES = [ LINE_COLORS_A, LINE_COLORS_B, @@ -180,23 +181,24 @@ export const LINE_COLOR_SCALES = [ LINE_COLORS_G, ].map(colorScale => { const name = colorScale[0].name - const colors = colorScale.map(color => color.hex) + const colors = colorScale const id = colorScale[0].id return {name, colors, id} }) -const paletteA = ['#31C0F6', '#A500A5', '#FF7E27'] -const paletteB = ['#74D495', '#3F3FBA', '#EA5994'] -const paletteC = ['#8F8AF4', '#A51414', '#F4CF31'] -const paletteD = ['#FD7A5D', '#5F1CF2', '#4CE09A'] -const paletteE = ['#FDC44F', '#007C76', '#8983FF'] -const paletteF = ['#DA6FF1', '#00717A', '#05B7E0'] -const paletteG = ['#F6F6F8', '#A4A8B6', '#545667'] - -export const generateColorScale = numSeries => { - return chroma - .scale(paletteB) - .mode('lch') - .colors(numSeries) +export const transformColorsForChroma = colors => { + return colors.map(color => color.hex) +} + +export const validateLineColors = colors => { + if (!colors || colors.length !== 3) { + return DEFAULT_LINE_COLORS + } + + const testColorsTypes = + colors.filter(color => color.type === COLOR_TYPE_SCALE).length === + colors.length + + return testColorsTypes ? colors : DEFAULT_LINE_COLORS }