Implement updating and rendering of line color scales

pull/10616/head
Alex P 2018-03-26 19:10:19 -07:00
parent c1f3de244c
commit a3fc9b778c
10 changed files with 223 additions and 67 deletions

View File

@ -57,3 +57,10 @@ export const updateTableOptions = tableOptions => ({
tableOptions,
},
})
export const updateLineColors = lineColors => ({
type: 'UPDATE_LINE_COLORS',
payload: {
lineColors,
},
})

View File

@ -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"
/>
</div>
<LineGraphColorSelector />
<div className="form-group col-sm-6">
<label htmlFor="min">Min</label>
<OptIn

View File

@ -107,7 +107,7 @@ class CellEditorOverlay extends Component {
handleSaveCell = () => {
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 = {

View File

@ -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 (
<div className="graph">
<VisualizationName />
<div className="graph-container">
<RefreshingGraph
colors={stringifyColorValues(colors)}
colors={colors}
axes={axes}
type={type}
tableOptions={tableOptions}
@ -87,6 +106,15 @@ DashVisualization.propTypes = {
value: number.isRequired,
}).isRequired
),
lineColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: number.isRequired,
}).isRequired
),
staticLegend: bool,
setDataLabels: func,
}
@ -103,11 +131,13 @@ const mapStateToProps = ({
cellEditorOverlay: {
thresholdsListColors,
gaugeColors,
lineColors,
cell: {type, axes, tableOptions},
},
}) => ({
gaugeColors,
thresholdsListColors,
lineColors,
type,
axes,
tableOptions,

View File

@ -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}
<DashboardHeader
@ -515,6 +517,7 @@ DashboardPage.propTypes = {
thresholdsListType: string.isRequired,
thresholdsListColors: arrayOf(shape({}).isRequired).isRequired,
gaugeColors: arrayOf(shape({}).isRequired).isRequired,
lineColors: arrayOf(shape({}).isRequired).isRequired,
}
const mapStateToProps = (state, {params: {dashboardID}}) => {
@ -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,
}
}

View File

@ -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

View File

@ -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}}) => ({

View File

@ -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}

View File

@ -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 (
<div className="form-group col-xs-12">
<label>Line Colors</label>
<ColorScaleDropdown
onChoose={this.handleSelectColors}
stretchToFit={true}
selected={lineColors}
/>
</div>
)
}
}
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
)

View File

@ -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
}