Merge pull request #3060 from influxdata/moar-colors-n-stuff

Introduce Customizable Line Graph Colors
pull/10616/head
Alex Paxton 2018-04-02 12:02:50 -07:00 committed by GitHub
commit f179936d7d
34 changed files with 651 additions and 210 deletions

View File

@ -3,6 +3,13 @@
### Features
1. [#2526](https://github.com/influxdata/chronograf/pull/2526): Add support for RS256/JWKS verification, support for id_token parsing (as in ADFS)
1. [#3060](https://github.com/influxdata/chronograf/pull/3060): Add ability to set a color palette for Line, Stacked, Step-Plot, and Bar graphs
### UI Improvements
### Bug Fixes
## v1.4.3.0 [unreleased]
1. [#3080](https://github.com/influxdata/chronograf/pull/3080): Add tabular data visualization option with features
1. [#3103](https://github.com/influxdata/chronograf/pull/3103): Add ability to clone dashboards

View File

@ -113,7 +113,7 @@ func HasCorrectAxes(c *chronograf.DashboardCell) error {
// HasCorrectColors verifies that the format of each color is correct
func HasCorrectColors(c *chronograf.DashboardCell) error {
for _, color := range c.CellColors {
if !oneOf(color.Type, "max", "min", "threshold", "text", "background") {
if !oneOf(color.Type, "max", "min", "threshold", "text", "background", "scale") {
return chronograf.ErrInvalidColorType
}
if len(color.Hex) != 7 {

View File

@ -116,6 +116,7 @@
"axios": "^0.13.1",
"bignumber.js": "^4.0.2",
"calculate-size": "^1.1.1",
"chroma-js": "^1.3.6",
"classnames": "^2.2.3",
"dygraphs": "2.1.0",
"eslint-plugin-babel": "^4.1.2",
@ -147,4 +148,4 @@
"rome": "^2.1.22",
"uuid": "^3.2.1"
}
}
}

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

@ -24,7 +24,8 @@ import {
import {OVERLAY_TECHNOLOGY} from 'src/shared/constants/classNames'
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
import {AUTO_GROUP_BY} from 'src/shared/constants'
import {stringifyColorValues} from 'src/shared/constants/colorOperations'
import {getCellTypeColors} from 'src/dashboards/constants/cellEditor'
import {colorsStringSchema, colorsNumberSchema} from 'shared/schemas'
class CellEditorOverlay extends Component {
constructor(props) {
@ -107,7 +108,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:'}
@ -120,20 +121,12 @@ class CellEditorOverlay extends Component {
}
})
let colors = []
switch (cell.type) {
case 'gauge': {
colors = stringifyColorValues(gaugeColors)
break
}
case 'single-stat':
case 'line-plus-single-stat':
case 'table': {
colors = stringifyColorValues(thresholdsListColors)
break
}
}
const colors = getCellTypeColors({
cellType: cell.type,
gaugeColors,
thresholdsListColors,
lineColors,
})
this.props.onSave({
...cell,
@ -390,8 +383,9 @@ CellEditorOverlay.propTypes = {
dashboardID: string.isRequired,
sources: arrayOf(shape()),
thresholdsListType: string.isRequired,
thresholdsListColors: arrayOf(shape({}).isRequired).isRequired,
gaugeColors: arrayOf(shape({}).isRequired).isRequired,
thresholdsListColors: colorsNumberSchema.isRequired,
gaugeColors: colorsNumberSchema.isRequired,
lineColors: colorsStringSchema.isRequired,
}
CEOBottom.propTypes = {

View File

@ -20,6 +20,7 @@ import {
updateGaugeColors,
updateAxes,
} from 'src/dashboards/actions/cellEditorOverlay'
import {colorsNumberSchema} from 'shared/schemas'
class GaugeOptions extends Component {
handleAddThreshold = () => {
@ -219,18 +220,10 @@ class GaugeOptions extends Component {
}
}
const {arrayOf, func, number, shape, string} = PropTypes
const {func, shape} = PropTypes
GaugeOptions.propTypes = {
gaugeColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: number.isRequired,
}).isRequired
),
gaugeColors: colorsNumberSchema,
handleUpdateGaugeColors: func.isRequired,
handleUpdateAxes: func.isRequired,
axes: shape({}).isRequired,

View File

@ -6,7 +6,8 @@ import RefreshingGraph from 'src/shared/components/RefreshingGraph'
import buildQueries from 'utils/buildQueriesForGraphs'
import VisualizationName from 'src/dashboards/components/VisualizationName'
import {stringifyColorValues} from 'src/shared/constants/colorOperations'
import {getCellTypeColors} from 'src/dashboards/constants/cellEditor'
import {colorsStringSchema, colorsNumberSchema} from 'shared/schemas'
const DashVisualization = (
{
@ -14,6 +15,7 @@ const DashVisualization = (
type,
templates,
timeRange,
lineColors,
autoRefresh,
gaugeColors,
queryConfigs,
@ -26,14 +28,19 @@ const DashVisualization = (
},
{source: {links: {proxy}}}
) => {
const colors = type === 'gauge' ? gaugeColors : thresholdsListColors
const colors = getCellTypeColors({
cellType: type,
gaugeColors,
thresholdsListColors,
lineColors,
})
return (
<div className="graph">
<VisualizationName />
<div className="graph-container">
<RefreshingGraph
colors={stringifyColorValues(colors)}
colors={colors}
axes={axes}
type={type}
tableOptions={tableOptions}
@ -69,24 +76,9 @@ DashVisualization.propTypes = {
}),
tableOptions: shape({}),
resizerTopHeight: number,
thresholdsListColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: number.isRequired,
}).isRequired
),
gaugeColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: number.isRequired,
}).isRequired
),
thresholdsListColors: colorsNumberSchema,
gaugeColors: colorsNumberSchema,
lineColors: colorsStringSchema,
staticLegend: bool,
setDataLabels: func,
}
@ -103,11 +95,13 @@ const mapStateToProps = ({
cellEditorOverlay: {
thresholdsListColors,
gaugeColors,
lineColors,
cell: {type, axes, tableOptions},
},
}) => ({
gaugeColors,
thresholdsListColors,
lineColors,
type,
axes,
tableOptions,

View File

@ -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 =
'<p><strong>K/M/B</strong> = Thousand / Million / Billion<br/><strong>K/M/G</strong> = Kilo / Mega / Giga </p>'
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
}

View File

@ -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}
<DashboardHeader
@ -530,8 +533,9 @@ DashboardPage.propTypes = {
handleDismissEditingAnnotation: func.isRequired,
selectedCell: shape({}),
thresholdsListType: string.isRequired,
thresholdsListColors: arrayOf(shape({}).isRequired).isRequired,
gaugeColors: arrayOf(shape({}).isRequired).isRequired,
thresholdsListColors: colorsNumberSchema.isRequired,
gaugeColors: colorsNumberSchema.isRequired,
lineColors: colorsStringSchema.isRequired,
}
const mapStateToProps = (state, {params: {dashboardID}}) => {
@ -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,
}
}

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

@ -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 (
<RefreshingGraph
colors={DEFAULT_LINE_COLORS}
axes={axes}
type={cellType}
queries={queries}

View File

@ -8,6 +8,8 @@ import underlayCallback from 'src/kapacitor/helpers/ruleGraphUnderlay'
const RefreshingLineGraph = AutoRefresh(LineGraph)
import {LINE_COLORS_RULE_GRAPH} from 'src/shared/constants/graphColorPalettes'
const {shape, string, func} = PropTypes
const RuleGraph = ({
query,
@ -20,7 +22,6 @@ const RuleGraph = ({
const autoRefreshMs = 30000
const queryText = buildInfluxQLQuery({lower}, query)
const queries = [{host: source.links.proxy, text: queryText}]
const kapacitorLineColors = ['#4ED8A0']
if (!queryText) {
return (
@ -47,7 +48,7 @@ const RuleGraph = ({
isGraphFilled={false}
ruleValues={rule.values}
autoRefresh={autoRefreshMs}
overrideLineColors={kapacitorLineColors}
colors={LINE_COLORS_RULE_GRAPH}
underlayCallback={underlayCallback(rule)}
/>
</div>

View File

@ -99,7 +99,7 @@ ColorDropdown.propTypes = {
shape({
hex: string.isRequired,
name: string.isRequired,
})
}).isRequired
).isRequired,
stretchToFit: bool,
disabled: bool,

View File

@ -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 (
<div className={dropdownClassNames}>
<div
className={toggleClassNames}
onClick={this.handleToggleMenu}
disabled={disabled}
>
<div
className="color-dropdown--swatches"
style={this.generateGradientStyle(selected)}
/>
<div className="color-dropdown--name">{selected[0].name}</div>
<span className="caret" />
</div>
{expanded ? (
<div className="color-dropdown--menu">
<FancyScrollbar autoHide={false} autoHeight={true}>
{LINE_COLOR_SCALES.map(colorScale => (
<div
className={
colorScale.name === selected[0].name
? 'color-dropdown--item active'
: 'color-dropdown--item'
}
key={uuid.v4()}
onClick={this.handleDropdownClick(colorScale)}
>
<div
className="color-dropdown--swatches"
style={this.generateGradientStyle(colorScale.colors)}
/>
<span className="color-dropdown--name">
{colorScale.name}
</span>
</div>
))}
</FancyScrollbar>
</div>
) : null}
</div>
)
}
}
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)

View File

@ -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 && (
<StaticLegend
dygraphSeries={this.hashColorDygraphSeries()}
dygraphSeries={this.colorDygraphSeries()}
dygraph={this.dygraph}
handleReceiveStaticLegendHeight={
this.handleReceiveStaticLegendHeight
@ -420,7 +438,12 @@ Dygraph.propTypes = {
isGraphFilled: bool,
isBarGraph: bool,
staticLegend: bool,
overrideLineColors: array,
overrideLineColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
}).isRequired
),
dygraphSeries: shape({}).isRequired,
ruleValues: shape({
operator: string,
@ -437,6 +460,7 @@ Dygraph.propTypes = {
onZoom: func,
mode: string,
children: node,
colors: colorsStringSchema.isRequired,
}
const mapStateToProps = ({annotations: {mode}}) => ({

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -86,10 +86,6 @@ class StaticLegend extends Component {
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>
))}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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