Ver Fonte

Rendering arrows for color picker, applying color changes to time series

Dominik Prokop há 7 anos atrás
pai
commit
c8ac23f3c1

+ 20 - 4
packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx

@@ -2,9 +2,9 @@ import React, { Component, createRef } from 'react';
 import PopperController from '../Tooltip/PopperController';
 import Popper from '../Tooltip/Popper';
 import { ColorPickerPopover } from './ColorPickerPopover';
-import { Themeable } from '../../types';
+import { Themeable, GrafanaTheme } from '../../types';
 
-export interface ColorPickerProps {
+export interface ColorPickerProps extends Themeable {
   color: string;
   onChange: (color: string) => void;
 }
@@ -13,13 +13,29 @@ export class ColorPicker extends Component<ColorPickerProps & Themeable, any> {
   private pickerTriggerRef = createRef<HTMLDivElement>();
 
   render() {
+    const { theme } = this.props;
     return (
-      <PopperController content={<ColorPickerPopover {...this.props} />}>
+      <PopperController placement="bottom-start"  content={<ColorPickerPopover {...this.props} />}>
         {(showPopper, hidePopper, popperProps) => {
           return (
             <>
               {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 className="sp-preview">

+ 3 - 3
packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
-import NamedColorsPicker from './NamedColorsPicker';
+import NamedColorsPicker from './NamedColorsPalette';
 import { getColorName } from '../..//utils/colorsPalette';
-import { SpectrumPicker } from './SpectrumPicker';
+import { SpectrumPalette } from './SpectrumPalette';
 import { ColorPickerProps } from './ColorPicker';
 import { GrafanaTheme, Themeable } from '../../types';
 
@@ -32,7 +32,7 @@ export class ColorPickerPopover extends React.Component<Props, State> {
     const { color, onChange, theme } = this.props;
 
     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} />
     );

+ 7 - 39
packages/grafana-ui/src/components/ColorPicker/NamedColorsPicker.tsx → packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx

@@ -1,11 +1,12 @@
 import React, { FunctionComponent } from 'react';
-import { find, upperFirst } from 'lodash';
-import { Color, ColorsPalete, ColorDefinition, getColorForTheme } from '../../utils/colorsPalette';
 import { Themeable } from '../../types';
+import { ColorDefinition, getColorForTheme } from '../../utils/colorsPalette';
+import { Color } from 'csstype';
+import { find, upperFirst } from 'lodash';
 
 type ColorChangeHandler = (color: ColorDefinition) => void;
 
-enum ColorSwatchVariant {
+export enum ColorSwatchVariant {
   Small = 'small',
   Large = 'large',
 }
@@ -50,14 +51,14 @@ const ColorSwatch: FunctionComponent<ColorSwatchProps> = ({
   );
 };
 
-interface ColorsGroupProps extends Themeable {
+interface NamedColorsGroupProps extends Themeable {
   colors: ColorDefinition[];
   selectedColor?: Color;
   onColorSelect: ColorChangeHandler;
   key?: string;
 }
 
-const ColorsGroup: FunctionComponent<ColorsGroupProps> = ({
+const NamedColorsGroup: FunctionComponent<NamedColorsGroupProps> = ({
   colors,
   selectedColor,
   onColorSelect,
@@ -100,37 +101,4 @@ const ColorsGroup: FunctionComponent<ColorsGroupProps> = ({
   );
 };
 
-interface NamedColorsPickerProps extends Themeable {
-  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;
+export default NamedColorsGroup;

+ 42 - 0
packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.tsx

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

+ 15 - 4
packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx

@@ -3,7 +3,7 @@ import * as PopperJS from 'popper.js';
 import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
 import PopperController from '../Tooltip/PopperController';
 import Popper from '../Tooltip/Popper';
-import { Themeable } from '../../types';
+import { Themeable, GrafanaTheme } from '../../types';
 import { ColorPickerProps } from './ColorPicker';
 
 export interface SeriesColorPickerProps extends ColorPickerProps, Themeable {
@@ -37,7 +37,7 @@ export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
   };
 
   render() {
-    const { children } = this.props;
+    const { children, theme } = this.props;
     return (
       <PopperController placement="bottom-start" content={this.renderPickerTabs()}>
         {(showPopper, hidePopper, popperProps) => {
@@ -49,10 +49,21 @@ export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
                   onMouseEnter={showPopper}
                   onMouseLeave={hidePopper}
                   referenceElement={this.pickerTriggerRef.current}
-                  className="ColorPicker"
-                  arrowClassName="popper__arrow"
+                  wrapperClassName="ColorPicker"
+                  renderArrow={({ arrowProps, placement }) => {
+                    return (
+                      <div
+                        {...arrowProps}
+                        data-placement={placement}
+                        className={`ColorPicker__arrow ColorPicker__arrow--${
+                          theme === GrafanaTheme.Light ? 'light' : 'dark'
+                        }`}
+                      />
+                    );
+                  }}
                 />
               )}
+
               {React.cloneElement(children, {
                 ref: this.pickerTriggerRef,
                 onClick: showPopper,

+ 1 - 1
packages/grafana-ui/src/components/ColorPicker/SpectrumPicker.tsx → packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx

@@ -9,7 +9,7 @@ export interface Props {
   onColorSelect: (color: string) => void;
 }
 
-export class SpectrumPicker extends React.Component<Props, any> {
+export class SpectrumPalette extends React.Component<Props, any> {
   elem: any;
   isMoving: boolean;
 

+ 99 - 4
packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss

@@ -1,12 +1,107 @@
+$arrowSize: 10px;
 .ColorPicker {
-  .popper__arrow {
-    border-color: #f7f8fa;
-  }
+  @extend .popper;
 }
 
 .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 {
   border-radius: 3px;
 }
@@ -93,7 +188,7 @@
 }
 
 .gf-color-picker__body {
-  padding-bottom: 10px;
+  padding-bottom: $arrowSize;
   padding-left: 6px;
 }
 

+ 15 - 10
packages/grafana-ui/src/components/Tooltip/Popper.tsx

@@ -1,6 +1,6 @@
 import React, { PureComponent } from 'react';
 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 Transition from 'react-transition-group/Transition';
 import { PopperContent } from './PopperController';
@@ -22,12 +22,18 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
   placement?: PopperJS.Placement;
   content: PopperContent;
   referenceElement: PopperJS.ReferenceObject;
-  arrowClassName?: string;
+  wrapperClassName?: string;
+  renderArrow?: (
+    props: {
+      arrowProps: PopperArrowProps;
+      placement: string;
+    }
+  ) => JSX.Element;
 }
 
 class Popper extends PureComponent<Props> {
   render() {
-    const { show, placement, onMouseEnter, onMouseLeave, className, arrowClassName } = this.props;
+    const { show, placement, onMouseEnter, onMouseLeave, className, wrapperClassName, renderArrow } = this.props;
     const { content } = this.props;
 
     return (
@@ -53,16 +59,15 @@ class Popper extends PureComponent<Props> {
                         ...transitionStyles[transitionState],
                       }}
                       data-placement={placement}
-                      className={`popper`}
+                      className={`${wrapperClassName}`}
                     >
                       <div className={className}>
                         {content}
-                        <div
-                          ref={arrowProps.ref}
-                          style={{ ...arrowProps.style }}
-                          data-placement={placement}
-                          className={arrowClassName}
-                        />
+                        {renderArrow &&
+                          renderArrow({
+                            arrowProps,
+                            placement,
+                          })}
                       </div>
                     </div>
                   );

+ 4 - 1
packages/grafana-ui/src/components/Tooltip/Tooltip.tsx

@@ -27,8 +27,11 @@ export const Tooltip = ({ children, theme, ...controllerProps }: TooltipProps) =
                 onMouseEnter={showPopper}
                 onMouseLeave={hidePopper}
                 referenceElement={tooltipTriggerRef.current}
+                wrapperClassName='popper'
                 className={popperBackgroundClassName}
-                arrowClassName={'popper__arrow'}
+                renderArrow={({ arrowProps, placement }) => (
+                  <div className="popper__arrow" data-placement={placement} {...arrowProps} />
+                )}
               />
             )}
             {React.cloneElement(children, {

+ 0 - 1
packages/grafana-ui/src/components/Tooltip/_Tooltip.scss

@@ -44,7 +44,6 @@ $popper-margin-from-ref: 5px;
   margin: 0px;
 }
 
-
 // Top
 .popper[data-placement^='top'] {
   padding-bottom: $popper-margin-from-ref;

+ 37 - 10
packages/grafana-ui/src/utils/colorsPalette.test.ts

@@ -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', () => {
-  const FakeBlue = buildColorDefinition('blue', 'blue', ['#0000ff', '#00000ee']);
-
-  beforeAll(() => {
-    ColorsPalete.set('blue', [FakeBlue]);
-  });
 
   describe('getColorDefinition', () => {
     it('returns undefined for unknown hex', () => {
@@ -13,8 +9,8 @@ describe('colors', () => {
     });
 
     it('returns definition for known hex', () => {
-      expect(getColorDefinition(FakeBlue.variants.light)).toEqual(FakeBlue);
-      expect(getColorDefinition(FakeBlue.variants.dark)).toEqual(FakeBlue);
+      expect(getColorDefinition(SemiDarkBlue.variants.light)).toEqual(SemiDarkBlue);
+      expect(getColorDefinition(SemiDarkBlue.variants.dark)).toEqual(SemiDarkBlue);
     });
   });
 
@@ -24,8 +20,39 @@ describe('colors', () => {
     });
 
     it('returns name for known hex', () => {
-      expect(getColorName(FakeBlue.variants.light)).toEqual(FakeBlue.name);
-      expect(getColorName(FakeBlue.variants.dark)).toEqual(FakeBlue.name);
+      expect(getColorName(SemiDarkBlue.variants.light)).toEqual(SemiDarkBlue.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)');
     });
   });
 });

+ 27 - 8
packages/grafana-ui/src/utils/colorsPalette.ts

@@ -47,7 +47,7 @@ export type ColorDefinition = {
   variants: ThemeVariants;
 };
 
-export const ColorsPalete = new Map<Hue, ColorDefinition[]>();
+export const ColorsPalette = new Map<Hue, ColorDefinition[]>();
 
 export const buildColorDefinition = (
   hue: Hue,
@@ -107,15 +107,15 @@ const blues = [BasicBlue, DarkBlue, SemiDarkBlue, LightBlue, SuperLightBlue];
 const oranges = [BasicOrange, DarkOrange, SemiDarkOrange, LightOrange, SuperLightOrange];
 const purples = [BasicPurple, DarkPurple, SemiDarkPurple, LightPurple, SuperLightPurple];
 
-ColorsPalete.set('green', greens);
-ColorsPalete.set('yellow', yellows);
-ColorsPalete.set('red', reds);
-ColorsPalete.set('blue', blues);
-ColorsPalete.set('orange', oranges);
-ColorsPalete.set('purple', purples);
+ColorsPalette.set('green', greens);
+ColorsPalette.set('yellow', yellows);
+ColorsPalette.set('red', reds);
+ColorsPalette.set('blue', blues);
+ColorsPalette.set('orange', oranges);
+ColorsPalette.set('purple', purples);
 
 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)
   )[0];
 };
@@ -137,6 +137,25 @@ export const getColorName = (color: string): Color | undefined => {
   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) => {
   return theme ? color.variants[theme] : color.variants.dark;
 };

+ 3 - 9
public/app/core/time_series2.ts

@@ -1,7 +1,6 @@
 import kbn from 'app/core/utils/kbn';
 import { getFlotTickDecimals } from 'app/core/utils/ticks';
 import _ from 'lodash';
-import { ColorDefinition } from '@grafana/ui/src/utils/colorsPalette';
 
 function matchSeriesOverride(aliasOrRegex, seriesAlias) {
   if (!aliasOrRegex) {
@@ -357,13 +356,8 @@ export default class TimeSeries {
     return false;
   }
 
-  setColor(color: string | ColorDefinition) {
-    if (typeof color === 'string') {
-      this.color = color;
-      this.bars.fillColor = color;
-    } else {
-      this.color = color.variants.dark;
-      this.bars.fillColor = color.variants.dark;
-    }
+  setColor(color: string) {
+    this.color = color;
+    this.bars.fillColor = color;
   }
 }

+ 5 - 2
public/app/plugins/panel/graph/data_processor.ts

@@ -1,7 +1,9 @@
 import _ from 'lodash';
-import { colors } from '@grafana/ui';
+import { colors, GrafanaTheme } from '@grafana/ui';
 
 import TimeSeries from 'app/core/time_series2';
+import { getColorFromHexRgbOrName } from '@grafana/ui/src/utils/colorsPalette';
+import config from 'app/core/config';
 
 export class DataProcessor {
   constructor(private panel) {}
@@ -107,12 +109,13 @@ export class DataProcessor {
     const alias = seriesData.target;
 
     const colorIndex = index % colors.length;
+
     const color = this.panel.aliasColors[alias] || colors[colorIndex];
 
     const series = new TimeSeries({
       datapoints: datapoints,
       alias: alias,
-      color: color,
+      color: getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark),
       unit: seriesData.unit,
     });
 

+ 5 - 2
public/app/plugins/panel/graph/module.ts

@@ -9,6 +9,9 @@ import _ from 'lodash';
 import { MetricsPanelCtrl } from 'app/plugins/sdk';
 import { DataProcessor } from './data_processor';
 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 {
   static template = template;
@@ -242,8 +245,8 @@ class GraphCtrl extends MetricsPanelCtrl {
   }
 
   onColorChange = (series, color) => {
-    series.setColor(color);
-    this.panel.aliasColors[series.alias] = series.color;
+    series.setColor(getColorFromHexRgbOrName(color, config.bootData.user.lightTheme ? GrafanaTheme.Light : GrafanaTheme.Dark));
+    this.panel.aliasColors[series.alias] = color;
     this.render();
   };