mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Rendering arrows for color picker, applying color changes to time series
This commit is contained in:
@@ -2,9 +2,9 @@ import React, { Component, createRef } from 'react';
|
|||||||
import PopperController from '../Tooltip/PopperController';
|
import PopperController from '../Tooltip/PopperController';
|
||||||
import Popper from '../Tooltip/Popper';
|
import Popper from '../Tooltip/Popper';
|
||||||
import { ColorPickerPopover } from './ColorPickerPopover';
|
import { ColorPickerPopover } from './ColorPickerPopover';
|
||||||
import { Themeable } from '../../types';
|
import { Themeable, GrafanaTheme } from '../../types';
|
||||||
|
|
||||||
export interface ColorPickerProps {
|
export interface ColorPickerProps extends Themeable {
|
||||||
color: string;
|
color: string;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
}
|
}
|
||||||
@@ -13,13 +13,29 @@ export class ColorPicker extends Component<ColorPickerProps & Themeable, any> {
|
|||||||
private pickerTriggerRef = createRef<HTMLDivElement>();
|
private pickerTriggerRef = createRef<HTMLDivElement>();
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { theme } = this.props;
|
||||||
return (
|
return (
|
||||||
<PopperController content={<ColorPickerPopover {...this.props} />}>
|
<PopperController placement="bottom-start" content={<ColorPickerPopover {...this.props} />}>
|
||||||
{(showPopper, hidePopper, popperProps) => {
|
{(showPopper, hidePopper, popperProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{this.pickerTriggerRef.current && (
|
{this.pickerTriggerRef.current && (
|
||||||
<Popper {...popperProps} referenceElement={this.pickerTriggerRef.current} className="ColorPicker" />
|
<Popper
|
||||||
|
{...popperProps}
|
||||||
|
referenceElement={this.pickerTriggerRef.current}
|
||||||
|
className="ColorPicker"
|
||||||
|
renderArrow={({ arrowProps, placement }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...arrowProps}
|
||||||
|
data-placement={placement}
|
||||||
|
className={`ColorPicker__arrow ColorPicker__arrow--${
|
||||||
|
theme === GrafanaTheme.Light ? 'light' : 'dark'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<div ref={this.pickerTriggerRef} onClick={showPopper} className="sp-replacer sp-light">
|
<div ref={this.pickerTriggerRef} onClick={showPopper} className="sp-replacer sp-light">
|
||||||
<div className="sp-preview">
|
<div className="sp-preview">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import NamedColorsPicker from './NamedColorsPicker';
|
import NamedColorsPicker from './NamedColorsPalette';
|
||||||
import { getColorName } from '../..//utils/colorsPalette';
|
import { getColorName } from '../..//utils/colorsPalette';
|
||||||
import { SpectrumPicker } from './SpectrumPicker';
|
import { SpectrumPalette } from './SpectrumPalette';
|
||||||
import { ColorPickerProps } from './ColorPicker';
|
import { ColorPickerProps } from './ColorPicker';
|
||||||
import { GrafanaTheme, Themeable } from '../../types';
|
import { GrafanaTheme, Themeable } from '../../types';
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ export class ColorPickerPopover extends React.Component<Props, State> {
|
|||||||
const { color, onChange, theme } = this.props;
|
const { color, onChange, theme } = this.props;
|
||||||
|
|
||||||
return activePicker === 'spectrum' ? (
|
return activePicker === 'spectrum' ? (
|
||||||
<SpectrumPicker color={color} onColorSelect={this.handleSpectrumColorSelect} options={{}} />
|
<SpectrumPalette color={color} onColorSelect={this.handleSpectrumColorSelect} options={{}} />
|
||||||
) : (
|
) : (
|
||||||
<NamedColorsPicker color={getColorName(color)} onChange={onChange} theme={theme} />
|
<NamedColorsPicker color={getColorName(color)} onChange={onChange} theme={theme} />
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React, { FunctionComponent } from 'react';
|
import React, { FunctionComponent } from 'react';
|
||||||
import { find, upperFirst } from 'lodash';
|
|
||||||
import { Color, ColorsPalete, ColorDefinition, getColorForTheme } from '../../utils/colorsPalette';
|
|
||||||
import { Themeable } from '../../types';
|
import { Themeable } from '../../types';
|
||||||
|
import { ColorDefinition, getColorForTheme } from '../../utils/colorsPalette';
|
||||||
|
import { Color } from 'csstype';
|
||||||
|
import { find, upperFirst } from 'lodash';
|
||||||
|
|
||||||
type ColorChangeHandler = (color: ColorDefinition) => void;
|
type ColorChangeHandler = (color: ColorDefinition) => void;
|
||||||
|
|
||||||
enum ColorSwatchVariant {
|
export enum ColorSwatchVariant {
|
||||||
Small = 'small',
|
Small = 'small',
|
||||||
Large = 'large',
|
Large = 'large',
|
||||||
}
|
}
|
||||||
@@ -50,14 +51,14 @@ const ColorSwatch: FunctionComponent<ColorSwatchProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ColorsGroupProps extends Themeable {
|
interface NamedColorsGroupProps extends Themeable {
|
||||||
colors: ColorDefinition[];
|
colors: ColorDefinition[];
|
||||||
selectedColor?: Color;
|
selectedColor?: Color;
|
||||||
onColorSelect: ColorChangeHandler;
|
onColorSelect: ColorChangeHandler;
|
||||||
key?: string;
|
key?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ColorsGroup: FunctionComponent<ColorsGroupProps> = ({
|
const NamedColorsGroup: FunctionComponent<NamedColorsGroupProps> = ({
|
||||||
colors,
|
colors,
|
||||||
selectedColor,
|
selectedColor,
|
||||||
onColorSelect,
|
onColorSelect,
|
||||||
@@ -100,37 +101,4 @@ const ColorsGroup: FunctionComponent<ColorsGroupProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface NamedColorsPickerProps extends Themeable {
|
export default NamedColorsGroup;
|
||||||
color?: Color;
|
|
||||||
onChange: (colorName: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NamedColorsPicker = ({ color, onChange, theme }: NamedColorsPickerProps) => {
|
|
||||||
const swatches: JSX.Element[] = [];
|
|
||||||
ColorsPalete.forEach((colors, hue) => {
|
|
||||||
swatches.push(
|
|
||||||
<ColorsGroup
|
|
||||||
key={hue}
|
|
||||||
theme={theme}
|
|
||||||
selectedColor={color}
|
|
||||||
colors={colors}
|
|
||||||
onColorSelect={color => onChange(color.name)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: 'repeat(3, 1fr)',
|
|
||||||
gridRowGap: '32px',
|
|
||||||
gridColumnGap: '32px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{swatches}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NamedColorsPicker;
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Color, ColorsPalette } from '../../utils/colorsPalette';
|
||||||
|
import { Themeable } from '../../types/index';
|
||||||
|
import NamedColorsGroup from './NamedColorsGroup';
|
||||||
|
|
||||||
|
interface NamedColorsPaletteProps extends Themeable {
|
||||||
|
color?: Color;
|
||||||
|
onChange: (colorName: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NamedColorsPalette = ({ color, onChange, theme }: NamedColorsPaletteProps) => {
|
||||||
|
const swatches: JSX.Element[] = [];
|
||||||
|
|
||||||
|
ColorsPalette.forEach((colors, hue) => {
|
||||||
|
swatches.push(
|
||||||
|
<NamedColorsGroup
|
||||||
|
key={hue}
|
||||||
|
theme={theme}
|
||||||
|
selectedColor={color}
|
||||||
|
colors={colors}
|
||||||
|
onColorSelect={color => {
|
||||||
|
onChange(color.name)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||||
|
gridRowGap: '32px',
|
||||||
|
gridColumnGap: '32px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{swatches}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NamedColorsPalette;
|
||||||
@@ -3,7 +3,7 @@ import * as PopperJS from 'popper.js';
|
|||||||
import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
|
import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
|
||||||
import PopperController from '../Tooltip/PopperController';
|
import PopperController from '../Tooltip/PopperController';
|
||||||
import Popper from '../Tooltip/Popper';
|
import Popper from '../Tooltip/Popper';
|
||||||
import { Themeable } from '../../types';
|
import { Themeable, GrafanaTheme } from '../../types';
|
||||||
import { ColorPickerProps } from './ColorPicker';
|
import { ColorPickerProps } from './ColorPicker';
|
||||||
|
|
||||||
export interface SeriesColorPickerProps extends ColorPickerProps, Themeable {
|
export interface SeriesColorPickerProps extends ColorPickerProps, Themeable {
|
||||||
@@ -37,7 +37,7 @@ export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { children } = this.props;
|
const { children, theme } = this.props;
|
||||||
return (
|
return (
|
||||||
<PopperController placement="bottom-start" content={this.renderPickerTabs()}>
|
<PopperController placement="bottom-start" content={this.renderPickerTabs()}>
|
||||||
{(showPopper, hidePopper, popperProps) => {
|
{(showPopper, hidePopper, popperProps) => {
|
||||||
@@ -49,10 +49,21 @@ export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
|
|||||||
onMouseEnter={showPopper}
|
onMouseEnter={showPopper}
|
||||||
onMouseLeave={hidePopper}
|
onMouseLeave={hidePopper}
|
||||||
referenceElement={this.pickerTriggerRef.current}
|
referenceElement={this.pickerTriggerRef.current}
|
||||||
className="ColorPicker"
|
wrapperClassName="ColorPicker"
|
||||||
arrowClassName="popper__arrow"
|
renderArrow={({ arrowProps, placement }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...arrowProps}
|
||||||
|
data-placement={placement}
|
||||||
|
className={`ColorPicker__arrow ColorPicker__arrow--${
|
||||||
|
theme === GrafanaTheme.Light ? 'light' : 'dark'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{React.cloneElement(children, {
|
{React.cloneElement(children, {
|
||||||
ref: this.pickerTriggerRef,
|
ref: this.pickerTriggerRef,
|
||||||
onClick: showPopper,
|
onClick: showPopper,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface Props {
|
|||||||
onColorSelect: (color: string) => void;
|
onColorSelect: (color: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SpectrumPicker extends React.Component<Props, any> {
|
export class SpectrumPalette extends React.Component<Props, any> {
|
||||||
elem: any;
|
elem: any;
|
||||||
isMoving: boolean;
|
isMoving: boolean;
|
||||||
|
|
||||||
@@ -1,12 +1,107 @@
|
|||||||
|
$arrowSize: 10px;
|
||||||
.ColorPicker {
|
.ColorPicker {
|
||||||
.popper__arrow {
|
@extend .popper;
|
||||||
border-color: #f7f8fa;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ColorPicker__arrow {
|
.ColorPicker__arrow {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-style: solid;
|
||||||
|
position: absolute;
|
||||||
|
margin: 0px;
|
||||||
|
|
||||||
|
&[data-placement^='top'] {
|
||||||
|
border-width: $arrowSize $arrowSize 0 $arrowSize;
|
||||||
|
border-left-color: transparent;
|
||||||
|
border-right-color: transparent;
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
bottom: -$arrowSize;
|
||||||
|
left: calc(50% - $arrowSize);
|
||||||
|
padding-top: $arrowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-placement^='bottom'] {
|
||||||
|
border-width: 0 $arrowSize $arrowSize $arrowSize;
|
||||||
|
border-left-color: transparent;
|
||||||
|
border-right-color: transparent;
|
||||||
|
border-top-color: transparent;
|
||||||
|
top: 0;
|
||||||
|
left: calc(50% - $arrowSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-placement^='bottom-start'] {
|
||||||
|
border-width: 0 $arrowSize $arrowSize $arrowSize;
|
||||||
|
border-left-color: transparent;
|
||||||
|
border-right-color: transparent;
|
||||||
|
border-top-color: transparent;
|
||||||
|
top: 0;
|
||||||
|
left: $arrowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-placement^='bottom-end'] & {
|
||||||
|
border-width: 0 $arrowSize $arrowSize $arrowSize;
|
||||||
|
border-left-color: transparent;
|
||||||
|
border-right-color: transparent;
|
||||||
|
border-top-color: transparent;
|
||||||
|
top: 0;
|
||||||
|
left: calc(100% - $arrowSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-placement^='right'] {
|
||||||
|
border-width: $arrowSize $arrowSize $arrowSize 0;
|
||||||
|
border-left-color: transparent;
|
||||||
|
border-top-color: transparent;
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
left: 0;
|
||||||
|
top: calc(50% - $arrowSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-placement^='left'] {
|
||||||
|
border-width: $arrowSize 0 $arrowSize $arrowSize;
|
||||||
|
border-top-color: transparent;
|
||||||
|
border-right-color: transparent;
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
right: -$arrowSize;
|
||||||
|
top: calc(50% - $arrowSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ColorPicker__arrow--light {
|
||||||
|
border-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ColorPicker__arrow--dark {
|
||||||
|
border-color: #1e2028;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top
|
||||||
|
.ColorPicker[data-placement^='top'] {
|
||||||
|
padding-bottom: $arrowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom
|
||||||
|
.ColorPicker[data-placement^='bottom'] {
|
||||||
|
padding-top: $arrowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ColorPicker[data-placement^='bottom-start'] {
|
||||||
|
padding-top: $arrowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ColorPicker[data-placement^='bottom-end'] {
|
||||||
|
padding-top: $arrowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right
|
||||||
|
.ColorPicker[data-placement^='right'] {
|
||||||
|
padding-left: $arrowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left
|
||||||
|
.ColorPicker[data-placement^='left'] {
|
||||||
|
padding-right: $arrowSize;
|
||||||
|
}
|
||||||
|
|
||||||
.ColorPickerPopover {
|
.ColorPickerPopover {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
@@ -93,7 +188,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.gf-color-picker__body {
|
.gf-color-picker__body {
|
||||||
padding-bottom: 10px;
|
padding-bottom: $arrowSize;
|
||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import * as PopperJS from 'popper.js';
|
import * as PopperJS from 'popper.js';
|
||||||
import { Manager, Popper as ReactPopper } from 'react-popper';
|
import { Manager, Popper as ReactPopper, PopperArrowProps } from 'react-popper';
|
||||||
import { Portal } from '@grafana/ui';
|
import { Portal } from '@grafana/ui';
|
||||||
import Transition from 'react-transition-group/Transition';
|
import Transition from 'react-transition-group/Transition';
|
||||||
import { PopperContent } from './PopperController';
|
import { PopperContent } from './PopperController';
|
||||||
@@ -22,12 +22,18 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
placement?: PopperJS.Placement;
|
placement?: PopperJS.Placement;
|
||||||
content: PopperContent;
|
content: PopperContent;
|
||||||
referenceElement: PopperJS.ReferenceObject;
|
referenceElement: PopperJS.ReferenceObject;
|
||||||
arrowClassName?: string;
|
wrapperClassName?: string;
|
||||||
|
renderArrow?: (
|
||||||
|
props: {
|
||||||
|
arrowProps: PopperArrowProps;
|
||||||
|
placement: string;
|
||||||
|
}
|
||||||
|
) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Popper extends PureComponent<Props> {
|
class Popper extends PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { show, placement, onMouseEnter, onMouseLeave, className, arrowClassName } = this.props;
|
const { show, placement, onMouseEnter, onMouseLeave, className, wrapperClassName, renderArrow } = this.props;
|
||||||
const { content } = this.props;
|
const { content } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -53,16 +59,15 @@ class Popper extends PureComponent<Props> {
|
|||||||
...transitionStyles[transitionState],
|
...transitionStyles[transitionState],
|
||||||
}}
|
}}
|
||||||
data-placement={placement}
|
data-placement={placement}
|
||||||
className={`popper`}
|
className={`${wrapperClassName}`}
|
||||||
>
|
>
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{content}
|
{content}
|
||||||
<div
|
{renderArrow &&
|
||||||
ref={arrowProps.ref}
|
renderArrow({
|
||||||
style={{ ...arrowProps.style }}
|
arrowProps,
|
||||||
data-placement={placement}
|
placement,
|
||||||
className={arrowClassName}
|
})}
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,8 +27,11 @@ export const Tooltip = ({ children, theme, ...controllerProps }: TooltipProps) =
|
|||||||
onMouseEnter={showPopper}
|
onMouseEnter={showPopper}
|
||||||
onMouseLeave={hidePopper}
|
onMouseLeave={hidePopper}
|
||||||
referenceElement={tooltipTriggerRef.current}
|
referenceElement={tooltipTriggerRef.current}
|
||||||
|
wrapperClassName='popper'
|
||||||
className={popperBackgroundClassName}
|
className={popperBackgroundClassName}
|
||||||
arrowClassName={'popper__arrow'}
|
renderArrow={({ arrowProps, placement }) => (
|
||||||
|
<div className="popper__arrow" data-placement={placement} {...arrowProps} />
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{React.cloneElement(children, {
|
{React.cloneElement(children, {
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ $popper-margin-from-ref: 5px;
|
|||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Top
|
// Top
|
||||||
.popper[data-placement^='top'] {
|
.popper[data-placement^='top'] {
|
||||||
padding-bottom: $popper-margin-from-ref;
|
padding-bottom: $popper-margin-from-ref;
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import { getColorName, getColorDefinition, ColorsPalete, buildColorDefinition } from './colorsPalette';
|
import { getColorName, getColorDefinition, getColorByName, SemiDarkBlue, getColorFromHexRgbOrName } from './colorsPalette';
|
||||||
|
import { GrafanaTheme } from '../types';
|
||||||
|
|
||||||
describe('colors', () => {
|
describe('colors', () => {
|
||||||
const FakeBlue = buildColorDefinition('blue', 'blue', ['#0000ff', '#00000ee']);
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
ColorsPalete.set('blue', [FakeBlue]);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getColorDefinition', () => {
|
describe('getColorDefinition', () => {
|
||||||
it('returns undefined for unknown hex', () => {
|
it('returns undefined for unknown hex', () => {
|
||||||
@@ -13,8 +9,8 @@ describe('colors', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns definition for known hex', () => {
|
it('returns definition for known hex', () => {
|
||||||
expect(getColorDefinition(FakeBlue.variants.light)).toEqual(FakeBlue);
|
expect(getColorDefinition(SemiDarkBlue.variants.light)).toEqual(SemiDarkBlue);
|
||||||
expect(getColorDefinition(FakeBlue.variants.dark)).toEqual(FakeBlue);
|
expect(getColorDefinition(SemiDarkBlue.variants.dark)).toEqual(SemiDarkBlue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -24,8 +20,39 @@ describe('colors', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns name for known hex', () => {
|
it('returns name for known hex', () => {
|
||||||
expect(getColorName(FakeBlue.variants.light)).toEqual(FakeBlue.name);
|
expect(getColorName(SemiDarkBlue.variants.light)).toEqual(SemiDarkBlue.name);
|
||||||
expect(getColorName(FakeBlue.variants.dark)).toEqual(FakeBlue.name);
|
expect(getColorName(SemiDarkBlue.variants.dark)).toEqual(SemiDarkBlue.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getColorByName', () => {
|
||||||
|
it('returns undefined for unknown color', () => {
|
||||||
|
expect(getColorByName('aruba-sunshine')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns color definiton for known color', () => {
|
||||||
|
expect(getColorByName(SemiDarkBlue.name)).toBe(SemiDarkBlue);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
describe('getColorFromHexRgbOrName', () => {
|
||||||
|
it('returns undefined for unknown color', () => {
|
||||||
|
expect(() => getColorFromHexRgbOrName('aruba-sunshine')).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns dark hex variant for known color if theme not specified', () => {
|
||||||
|
expect(getColorFromHexRgbOrName(SemiDarkBlue.name)).toBe(SemiDarkBlue.variants.dark);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct variant\'s hex for known color if theme specified', () => {
|
||||||
|
expect(getColorFromHexRgbOrName(SemiDarkBlue.name, GrafanaTheme.Light)).toBe(SemiDarkBlue.variants.light);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns color if specified as hex or rgb/a', () => {
|
||||||
|
expect(getColorFromHexRgbOrName('ff0000')).toBe('ff0000');
|
||||||
|
expect(getColorFromHexRgbOrName('#ff0000')).toBe('#ff0000');
|
||||||
|
expect(getColorFromHexRgbOrName('rgb(0,0,0)')).toBe('rgb(0,0,0)');
|
||||||
|
expect(getColorFromHexRgbOrName('rgba(0,0,0,1)')).toBe('rgba(0,0,0,1)');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export type ColorDefinition = {
|
|||||||
variants: ThemeVariants;
|
variants: ThemeVariants;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ColorsPalete = new Map<Hue, ColorDefinition[]>();
|
export const ColorsPalette = new Map<Hue, ColorDefinition[]>();
|
||||||
|
|
||||||
export const buildColorDefinition = (
|
export const buildColorDefinition = (
|
||||||
hue: Hue,
|
hue: Hue,
|
||||||
@@ -107,15 +107,15 @@ const blues = [BasicBlue, DarkBlue, SemiDarkBlue, LightBlue, SuperLightBlue];
|
|||||||
const oranges = [BasicOrange, DarkOrange, SemiDarkOrange, LightOrange, SuperLightOrange];
|
const oranges = [BasicOrange, DarkOrange, SemiDarkOrange, LightOrange, SuperLightOrange];
|
||||||
const purples = [BasicPurple, DarkPurple, SemiDarkPurple, LightPurple, SuperLightPurple];
|
const purples = [BasicPurple, DarkPurple, SemiDarkPurple, LightPurple, SuperLightPurple];
|
||||||
|
|
||||||
ColorsPalete.set('green', greens);
|
ColorsPalette.set('green', greens);
|
||||||
ColorsPalete.set('yellow', yellows);
|
ColorsPalette.set('yellow', yellows);
|
||||||
ColorsPalete.set('red', reds);
|
ColorsPalette.set('red', reds);
|
||||||
ColorsPalete.set('blue', blues);
|
ColorsPalette.set('blue', blues);
|
||||||
ColorsPalete.set('orange', oranges);
|
ColorsPalette.set('orange', oranges);
|
||||||
ColorsPalete.set('purple', purples);
|
ColorsPalette.set('purple', purples);
|
||||||
|
|
||||||
export const getColorDefinition = (hex: string): ColorDefinition | undefined => {
|
export const getColorDefinition = (hex: string): ColorDefinition | undefined => {
|
||||||
return flatten(Array.from(ColorsPalete.values())).filter(definition =>
|
return flatten(Array.from(ColorsPalette.values())).filter(definition =>
|
||||||
some(values(definition.variants), color => color === hex)
|
some(values(definition.variants), color => color === hex)
|
||||||
)[0];
|
)[0];
|
||||||
};
|
};
|
||||||
@@ -137,6 +137,25 @@ export const getColorName = (color: string): Color | undefined => {
|
|||||||
return color as Color;
|
return color as Color;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getColorByName = (colorName: string) => {
|
||||||
|
const definition = flatten(Array.from(ColorsPalette.values())).filter(definition => definition.name === colorName);
|
||||||
|
return definition.length > 0 ? definition[0] : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaTheme): string => {
|
||||||
|
if (color.indexOf('rgb') > -1 || isHex(color)) {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorDefinition = getColorByName(color);
|
||||||
|
|
||||||
|
if (!colorDefinition) {
|
||||||
|
throw new Error('Unknown color');
|
||||||
|
}
|
||||||
|
|
||||||
|
return theme ? colorDefinition.variants[theme] : colorDefinition.variants.dark;
|
||||||
|
};
|
||||||
|
|
||||||
export const getColorForTheme = (color: ColorDefinition, theme?: GrafanaTheme) => {
|
export const getColorForTheme = (color: ColorDefinition, theme?: GrafanaTheme) => {
|
||||||
return theme ? color.variants[theme] : color.variants.dark;
|
return theme ? color.variants[theme] : color.variants.dark;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
import { getFlotTickDecimals } from 'app/core/utils/ticks';
|
import { getFlotTickDecimals } from 'app/core/utils/ticks';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { ColorDefinition } from '@grafana/ui/src/utils/colorsPalette';
|
|
||||||
|
|
||||||
function matchSeriesOverride(aliasOrRegex, seriesAlias) {
|
function matchSeriesOverride(aliasOrRegex, seriesAlias) {
|
||||||
if (!aliasOrRegex) {
|
if (!aliasOrRegex) {
|
||||||
@@ -357,13 +356,8 @@ export default class TimeSeries {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setColor(color: string | ColorDefinition) {
|
setColor(color: string) {
|
||||||
if (typeof color === 'string') {
|
this.color = color;
|
||||||
this.color = color;
|
this.bars.fillColor = color;
|
||||||
this.bars.fillColor = color;
|
|
||||||
} else {
|
|
||||||
this.color = color.variants.dark;
|
|
||||||
this.bars.fillColor = color.variants.dark;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { colors } from '@grafana/ui';
|
import { colors, GrafanaTheme } from '@grafana/ui';
|
||||||
|
|
||||||
import TimeSeries from 'app/core/time_series2';
|
import TimeSeries from 'app/core/time_series2';
|
||||||
|
import { getColorFromHexRgbOrName } from '@grafana/ui/src/utils/colorsPalette';
|
||||||
|
import config from 'app/core/config';
|
||||||
|
|
||||||
export class DataProcessor {
|
export class DataProcessor {
|
||||||
constructor(private panel) {}
|
constructor(private panel) {}
|
||||||
@@ -107,12 +109,13 @@ export class DataProcessor {
|
|||||||
const alias = seriesData.target;
|
const alias = seriesData.target;
|
||||||
|
|
||||||
const colorIndex = index % colors.length;
|
const colorIndex = index % colors.length;
|
||||||
|
|
||||||
const color = this.panel.aliasColors[alias] || colors[colorIndex];
|
const color = this.panel.aliasColors[alias] || colors[colorIndex];
|
||||||
|
|
||||||
const series = new TimeSeries({
|
const series = new TimeSeries({
|
||||||
datapoints: datapoints,
|
datapoints: datapoints,
|
||||||
alias: alias,
|
alias: alias,
|
||||||
color: color,
|
color: getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark),
|
||||||
unit: seriesData.unit,
|
unit: seriesData.unit,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import _ from 'lodash';
|
|||||||
import { MetricsPanelCtrl } from 'app/plugins/sdk';
|
import { MetricsPanelCtrl } from 'app/plugins/sdk';
|
||||||
import { DataProcessor } from './data_processor';
|
import { DataProcessor } from './data_processor';
|
||||||
import { axesEditorComponent } from './axes_editor';
|
import { axesEditorComponent } from './axes_editor';
|
||||||
|
import { getColorFromHexRgbOrName } from '@grafana/ui/src/utils/colorsPalette';
|
||||||
|
import config from 'app/core/config';
|
||||||
|
import { GrafanaTheme } from '@grafana/ui';
|
||||||
|
|
||||||
class GraphCtrl extends MetricsPanelCtrl {
|
class GraphCtrl extends MetricsPanelCtrl {
|
||||||
static template = template;
|
static template = template;
|
||||||
@@ -242,8 +245,8 @@ class GraphCtrl extends MetricsPanelCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onColorChange = (series, color) => {
|
onColorChange = (series, color) => {
|
||||||
series.setColor(color);
|
series.setColor(getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark));
|
||||||
this.panel.aliasColors[series.alias] = series.color;
|
this.panel.aliasColors[series.alias] = color;
|
||||||
this.render();
|
this.render();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user