瀏覽代碼

graph legend: refactor

Alexander Zobnin 7 年之前
父節點
當前提交
fe0c5c73dd

+ 0 - 2
public/app/core/angular_wrappers.ts

@@ -5,7 +5,6 @@ import EmptyListCTA from './components/EmptyListCTA/EmptyListCTA';
 import { SearchResult } from './components/search/SearchResult';
 import { TagFilter } from './components/TagFilter/TagFilter';
 import { SideMenu } from './components/sidemenu/SideMenu';
-import { GraphLegend } from 'app/plugins/panel/graph/Legend';
 
 export function registerAngularDirectives() {
   react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
@@ -18,5 +17,4 @@ export function registerAngularDirectives() {
     ['onSelect', { watchDepth: 'reference' }],
     ['tagOptions', { watchDepth: 'reference' }],
   ]);
-  react2AngularDirective('graphLegendReact', GraphLegend, ['seriesList', 'className']);
 }

+ 34 - 20
public/app/core/components/colorpicker/SeriesColorPicker.tsx

@@ -2,30 +2,53 @@ import React from 'react';
 import { ColorPickerPopover } from './ColorPickerPopover';
 import { react2AngularDirective } from 'app/core/utils/react2angular';
 
-export interface Props {
-  series: any;
+export interface SeriesColorPickerProps {
+  // series: any;
+  color: string;
+  yaxis?: number;
   onColorChange: (color: string) => void;
+  onToggleAxis?: () => void;
+}
+
+export class SeriesColorPicker extends React.PureComponent<SeriesColorPickerProps, any> {
+  render() {
+    return (
+      <div className="graph-legend-popover">
+        {this.props.yaxis && <AxisSelector yaxis={this.props.yaxis} onToggleAxis={this.props.onToggleAxis} />}
+        <ColorPickerPopover color={this.props.color} onColorSelect={this.props.onColorChange} />
+      </div>
+    );
+  }
+}
+
+interface AxisSelectorProps {
+  yaxis: number;
   onToggleAxis: () => void;
 }
 
-export class SeriesColorPicker extends React.Component<Props, any> {
+interface AxisSelectorState {
+  yaxis: number;
+}
+
+export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSelectorState> {
   constructor(props) {
     super(props);
-    this.onColorChange = this.onColorChange.bind(this);
+    this.state = {
+      yaxis: this.props.yaxis,
+    };
     this.onToggleAxis = this.onToggleAxis.bind(this);
   }
 
-  onColorChange(color) {
-    this.props.onColorChange(color);
-  }
-
   onToggleAxis() {
+    this.setState({
+      yaxis: this.state.yaxis === 2 ? 1 : 2,
+    });
     this.props.onToggleAxis();
   }
 
-  renderAxisSelection() {
-    const leftButtonClass = this.props.series.yaxis === 1 ? 'btn-success' : 'btn-inverse';
-    const rightButtonClass = this.props.series.yaxis === 2 ? 'btn-success' : 'btn-inverse';
+  render() {
+    const leftButtonClass = this.state.yaxis === 1 ? 'btn-success' : 'btn-inverse';
+    const rightButtonClass = this.state.yaxis === 2 ? 'btn-success' : 'btn-inverse';
 
     return (
       <div className="p-b-1">
@@ -39,15 +62,6 @@ export class SeriesColorPicker extends React.Component<Props, any> {
       </div>
     );
   }
-
-  render() {
-    return (
-      <div className="graph-legend-popover">
-        {this.props.series.yaxis && this.renderAxisSelection()}
-        <ColorPickerPopover color={this.props.series.color} onColorSelect={this.onColorChange} />
-      </div>
-    );
-  }
 }
 
 react2AngularDirective('seriesColorPicker', SeriesColorPicker, ['series', 'onColorChange', 'onToggleAxis']);

+ 83 - 0
public/app/core/components/colorpicker/withColorPicker.tsx

@@ -0,0 +1,83 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Drop from 'tether-drop';
+import { SeriesColorPicker } from './SeriesColorPicker';
+
+export interface WithSeriesColorPickerProps {
+  color: string;
+  yaxis?: number;
+  optionalClass?: string;
+  onColorChange: (newColor: string) => void;
+  onToggleAxis?: () => void;
+}
+
+export default function withSeriesColorPicker(WrappedComponent) {
+  return class extends React.Component<WithSeriesColorPickerProps, any> {
+    pickerElem: any;
+    colorPickerDrop: any;
+
+    static defaultProps = {
+      optionalClass: '',
+      yaxis: undefined,
+      onToggleAxis: () => {},
+    };
+
+    constructor(props) {
+      super(props);
+      this.openColorPicker = this.openColorPicker.bind(this);
+    }
+
+    openColorPicker() {
+      if (this.colorPickerDrop) {
+        this.destroyDrop();
+      }
+
+      const { color, yaxis, onColorChange, onToggleAxis } = this.props;
+      const dropContent = (
+        <SeriesColorPicker color={color} yaxis={yaxis} onColorChange={onColorChange} onToggleAxis={onToggleAxis} />
+      );
+      const dropContentElem = document.createElement('div');
+      ReactDOM.render(dropContent, dropContentElem);
+
+      const drop = new Drop({
+        target: this.pickerElem,
+        content: dropContentElem,
+        position: 'top center',
+        classes: 'drop-popover',
+        openOn: 'hover',
+        hoverCloseDelay: 200,
+        remove: true,
+        tetherOptions: {
+          constraints: [{ to: 'scrollParent', attachment: 'none both' }],
+        },
+      });
+
+      drop.on('close', this.closeColorPicker.bind(this));
+
+      this.colorPickerDrop = drop;
+      this.colorPickerDrop.open();
+    }
+
+    closeColorPicker() {
+      setTimeout(() => {
+        this.destroyDrop();
+      }, 100);
+    }
+
+    destroyDrop() {
+      if (this.colorPickerDrop && this.colorPickerDrop.tether) {
+        this.colorPickerDrop.destroy();
+        this.colorPickerDrop = null;
+      }
+    }
+
+    render() {
+      const { optionalClass, onColorChange, ...wrappedComponentProps } = this.props;
+      return (
+        <div className={optionalClass} ref={e => (this.pickerElem = e)} onClick={this.openColorPicker}>
+          <WrappedComponent {...wrappedComponentProps} />
+        </div>
+      );
+    }
+  };
+}

+ 29 - 186
public/app/plugins/panel/graph/Legend.tsx → public/app/plugins/panel/graph/Legend/Legend.tsx

@@ -1,18 +1,15 @@
 import _ from 'lodash';
 import React from 'react';
-import ReactDOM from 'react-dom';
 import { TimeSeries } from 'app/core/core';
 import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
-import Drop from 'tether-drop';
-import { ColorPickerPopover } from 'app/core/components/colorpicker/ColorPickerPopover';
-
-const LEGEND_STATS = ['min', 'max', 'avg', 'current', 'total'];
+import { LegendItem, LEGEND_STATS } from './LegendSeriesItem';
 
 interface LegendProps {
   seriesList: TimeSeries[];
   optionalClass?: string;
   onToggleSeries?: (series: TimeSeries, event: Event) => void;
   onToggleSort?: (sortBy, sortDesc) => void;
+  onToggleAxis?: (series: TimeSeries) => void;
   onColorChange?: (series: TimeSeries, color: string) => void;
 }
 
@@ -41,24 +38,24 @@ interface LegendSortProps {
 
 export type GraphLegendProps = LegendProps & LegendDisplayProps & LegendValuesProps & LegendSortProps;
 
-const defaultGraphLegendProps: Partial<GraphLegendProps> = {
-  values: false,
-  min: false,
-  max: false,
-  avg: false,
-  current: false,
-  total: false,
-  alignAsTable: false,
-  rightSide: false,
-  sort: undefined,
-  sortDesc: false,
-  optionalClass: '',
-};
-
-export interface GraphLegendState {}
-
-export class GraphLegend extends React.PureComponent<GraphLegendProps, GraphLegendState> {
-  static defaultProps = defaultGraphLegendProps;
+export class GraphLegend extends React.PureComponent<GraphLegendProps> {
+  static defaultProps: Partial<GraphLegendProps> = {
+    values: false,
+    min: false,
+    max: false,
+    avg: false,
+    current: false,
+    total: false,
+    alignAsTable: false,
+    rightSide: false,
+    sort: undefined,
+    sortDesc: false,
+    optionalClass: '',
+    onToggleSeries: () => {},
+    onToggleSort: () => {},
+    onToggleAxis: () => {},
+    onColorChange: () => {},
+  };
 
   sortLegend() {
     let seriesList = this.props.seriesList || [];
@@ -107,6 +104,7 @@ export class GraphLegend extends React.PureComponent<GraphLegendProps, GraphLege
       onToggleSeries: (s, e) => this.onToggleSeries(s, e),
       onToggleSort: (sortBy, sortDesc) => this.props.onToggleSort(sortBy, sortDesc),
       onColorChange: (series, color) => this.props.onColorChange(series, color),
+      onToggleAxis: series => this.props.onToggleAxis(series),
       ...seriesValuesProps,
       ...sortProps,
     };
@@ -124,7 +122,7 @@ class LegendSeriesList extends React.PureComponent<GraphLegendProps> {
     const { seriesList, hiddenSeries, values, min, max, avg, current, total } = this.props;
     const seriesValuesProps = { values, min, max, avg, current, total };
     return seriesList.map((series, i) => (
-      <LegendSeriesItem
+      <LegendItem
         key={series.id}
         series={series}
         index={i}
@@ -132,137 +130,12 @@ class LegendSeriesList extends React.PureComponent<GraphLegendProps> {
         {...seriesValuesProps}
         onLabelClick={e => this.props.onToggleSeries(series, e)}
         onColorChange={color => this.props.onColorChange(series, color)}
+        onToggleAxis={() => this.props.onToggleAxis(series)}
       />
     ));
   }
 }
 
-interface LegendSeriesProps {
-  series: TimeSeries;
-  index: number;
-  onLabelClick?: (event) => void;
-  onColorChange?: (color: string) => void;
-}
-
-type LegendSeriesItemProps = LegendSeriesProps & LegendDisplayProps & LegendValuesProps;
-
-class LegendSeriesItem extends React.PureComponent<LegendSeriesItemProps> {
-  render() {
-    const { series, index, hiddenSeries } = this.props;
-    const seriesOptionClasses = getOptionSeriesCSSClasses(series, hiddenSeries);
-    const valueItems = this.props.values ? renderLegendValues(this.props, series) : [];
-    return (
-      <div className={`graph-legend-series ${seriesOptionClasses}`} data-series-index={index}>
-        <LegendSeriesLabel
-          label={series.aliasEscaped}
-          color={series.color}
-          onLabelClick={e => this.props.onLabelClick(e)}
-          onColorChange={e => this.props.onColorChange(e)}
-        />
-        {valueItems}
-      </div>
-    );
-  }
-}
-
-interface LegendSeriesLabelProps {
-  label: string;
-  color: string;
-  onLabelClick?: (event) => void;
-  onColorChange?: (color: string) => void;
-}
-
-class LegendSeriesLabel extends React.PureComponent<LegendSeriesLabelProps> {
-  pickerElem: any;
-  colorPickerDrop: any;
-
-  openColorPicker() {
-    if (this.colorPickerDrop) {
-      this.destroyDrop();
-    }
-
-    const dropContent = <ColorPickerPopover color={this.props.color} onColorSelect={this.props.onColorChange} />;
-    const dropContentElem = document.createElement('div');
-    ReactDOM.render(dropContent, dropContentElem);
-
-    const drop = new Drop({
-      target: this.pickerElem,
-      content: dropContentElem,
-      position: 'top center',
-      classes: 'drop-popover',
-      openOn: 'hover',
-      hoverCloseDelay: 200,
-      remove: true,
-      tetherOptions: {
-        constraints: [{ to: 'scrollParent', attachment: 'none both' }],
-      },
-    });
-
-    drop.on('close', this.closeColorPicker.bind(this));
-
-    this.colorPickerDrop = drop;
-    this.colorPickerDrop.open();
-  }
-
-  closeColorPicker() {
-    setTimeout(() => {
-      this.destroyDrop();
-    }, 100);
-  }
-
-  destroyDrop() {
-    if (this.colorPickerDrop && this.colorPickerDrop.tether) {
-      this.colorPickerDrop.destroy();
-      this.colorPickerDrop = null;
-    }
-  }
-
-  render() {
-    const { label, color } = this.props;
-    return [
-      <div
-        key="icon"
-        className="graph-legend-icon"
-        ref={e => (this.pickerElem = e)}
-        onClick={() => this.openColorPicker()}
-      >
-        <i className="fa fa-minus pointer" style={{ color: color }} />
-      </div>,
-      <a className="graph-legend-alias pointer" title={label} key="label" onClick={e => this.props.onLabelClick(e)}>
-        {label}
-      </a>,
-    ];
-  }
-}
-
-interface LegendValueProps {
-  value: string;
-  valueName: string;
-  asTable?: boolean;
-}
-
-function LegendValue(props: LegendValueProps) {
-  const value = props.value;
-  const valueName = props.valueName;
-  if (props.asTable) {
-    return <td className={`graph-legend-value ${valueName}`}>{value}</td>;
-  }
-  return <div className={`graph-legend-value ${valueName}`}>{value}</div>;
-}
-
-function renderLegendValues(props: LegendSeriesItemProps, series, asTable = false) {
-  const legendValueItems = [];
-  for (const valueName of LEGEND_STATS) {
-    if (props[valueName]) {
-      const valueFormatted = series.formatValue(series.stats[valueName]);
-      legendValueItems.push(
-        <LegendValue key={valueName} valueName={valueName} value={valueFormatted} asTable={asTable} />
-      );
-    }
-  }
-  return legendValueItems;
-}
-
 class LegendTable extends React.PureComponent<Partial<GraphLegendProps>> {
   onToggleSort(stat) {
     let sortDesc = this.props.sortDesc;
@@ -305,14 +178,16 @@ class LegendTable extends React.PureComponent<Partial<GraphLegendProps>> {
             )}
           </tr>
           {seriesList.map((series, i) => (
-            <LegendSeriesItemAsTable
+            <LegendItem
               key={series.id}
+              asTable={true}
               series={series}
               index={i}
               hiddenSeries={this.props.hiddenSeries}
-              {...seriesValuesProps}
               onLabelClick={e => this.props.onToggleSeries(series, e)}
               onColorChange={color => this.props.onColorChange(series, color)}
+              onToggleAxis={() => this.props.onToggleAxis(series)}
+              {...seriesValuesProps}
             />
           ))}
         </tbody>
@@ -329,46 +204,14 @@ interface LegendTableHeaderProps {
 function LegendTableHeaderItem(props: LegendTableHeaderProps & LegendSortProps) {
   const { statName, sort, sortDesc } = props;
   return (
-    <th className="pointer" data-stat={statName} onClick={e => props.onClick(e)}>
+    <th className="pointer" onClick={e => props.onClick(e)}>
       {statName}
       {sort === statName && <span className={sortDesc ? 'fa fa-caret-down' : 'fa fa-caret-up'} />}
     </th>
   );
 }
 
-class LegendSeriesItemAsTable extends React.PureComponent<LegendSeriesItemProps> {
-  render() {
-    const { series, index, hiddenSeries } = this.props;
-    const seriesOptionClasses = getOptionSeriesCSSClasses(series, hiddenSeries);
-    const valueItems = this.props.values ? renderLegendValues(this.props, series, true) : [];
-    return (
-      <tr className={`graph-legend-series ${seriesOptionClasses}`} data-series-index={index}>
-        <td>
-          <LegendSeriesLabel
-            label={series.aliasEscaped}
-            color={series.color}
-            onLabelClick={e => this.props.onLabelClick(e)}
-            onColorChange={e => this.props.onColorChange(e)}
-          />
-        </td>
-        {valueItems}
-      </tr>
-    );
-  }
-}
-
-function getOptionSeriesCSSClasses(series, hiddenSeries) {
-  const classes = [];
-  if (series.yaxis === 2) {
-    classes.push('graph-legend-series--right-y');
-  }
-  if (hiddenSeries[series.alias] && hiddenSeries[series.alias] === true) {
-    classes.push('graph-legend-series-hidden');
-  }
-  return classes.join(' ');
-}
-
-export class Legend extends React.Component<GraphLegendProps, GraphLegendState> {
+export class Legend extends React.Component<GraphLegendProps> {
   render() {
     return (
       <CustomScrollbar>

+ 173 - 0
public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx

@@ -0,0 +1,173 @@
+import React from 'react';
+import { TimeSeries } from 'app/core/core';
+import withColorPicker from 'app/core/components/colorpicker/withColorPicker';
+
+export const LEGEND_STATS = ['min', 'max', 'avg', 'current', 'total'];
+
+export interface LegendLabelProps {
+  index: number;
+  series: TimeSeries;
+  asTable?: boolean;
+  hiddenSeries?: any;
+  onLabelClick?: (event) => void;
+  onColorChange?: (color: string) => void;
+  onToggleAxis?: () => void;
+}
+
+export interface LegendValuesProps {
+  values?: boolean;
+  min?: boolean;
+  max?: boolean;
+  avg?: boolean;
+  current?: boolean;
+  total?: boolean;
+}
+
+type LegendItemProps = LegendLabelProps & LegendValuesProps;
+
+export class LegendItem extends React.PureComponent<LegendItemProps> {
+  static defaultProps = {
+    asTable: false,
+    hiddenSeries: undefined,
+    onLabelClick: () => {},
+    onColorChange: () => {},
+    onToggleAxis: () => {},
+  };
+
+  render() {
+    const { series, hiddenSeries, asTable } = this.props;
+    const { aliasEscaped, color, yaxis } = this.props.series;
+    const seriesOptionClasses = getOptionSeriesCSSClasses(series, hiddenSeries);
+    const valueItems = this.props.values ? renderLegendValues(this.props, series, asTable) : [];
+    const seriesLabel = (
+      <LegendSeriesLabel
+        label={aliasEscaped}
+        color={color}
+        yaxis={yaxis}
+        onLabelClick={this.props.onLabelClick}
+        onColorChange={this.props.onColorChange}
+        onToggleAxis={this.props.onToggleAxis}
+      />
+    );
+
+    if (asTable) {
+      return (
+        <tr className={`graph-legend-series ${seriesOptionClasses}`}>
+          <td>{seriesLabel}</td>
+          {valueItems}
+        </tr>
+      );
+    } else {
+      return (
+        <div className={`graph-legend-series ${seriesOptionClasses}`}>
+          {seriesLabel}
+          {valueItems}
+        </div>
+      );
+    }
+  }
+}
+
+interface LegendSeriesLabelProps {
+  label: string;
+  color: string;
+  yaxis?: number;
+  onLabelClick?: (event) => void;
+}
+
+class LegendSeriesLabel extends React.PureComponent<LegendSeriesLabelProps & LegendSeriesIconProps> {
+  static defaultProps = {
+    yaxis: undefined,
+    onLabelClick: () => {},
+  };
+
+  render() {
+    const { label, color, yaxis } = this.props;
+    const { onColorChange, onToggleAxis } = this.props;
+    return [
+      <LegendSeriesIcon
+        key="icon"
+        color={color}
+        yaxis={yaxis}
+        onColorChange={onColorChange}
+        onToggleAxis={onToggleAxis}
+      />,
+      <a className="graph-legend-alias pointer" title={label} key="label" onClick={e => this.props.onLabelClick(e)}>
+        {label}
+      </a>,
+    ];
+  }
+}
+
+interface LegendSeriesIconProps {
+  color: string;
+  yaxis?: number;
+  onColorChange?: (color: string) => void;
+  onToggleAxis?: () => void;
+}
+
+function SeriesIcon(props) {
+  return <i className="fa fa-minus pointer" style={{ color: props.color }} />;
+}
+
+class LegendSeriesIcon extends React.PureComponent<LegendSeriesIconProps> {
+  static defaultProps = {
+    yaxis: undefined,
+    onColorChange: () => {},
+    onToggleAxis: () => {},
+  };
+
+  render() {
+    const { color, yaxis } = this.props;
+    const IconWithColorPicker = withColorPicker(SeriesIcon);
+
+    return (
+      <IconWithColorPicker
+        optionalClass="graph-legend-icon"
+        color={color}
+        yaxis={yaxis}
+        onColorChange={this.props.onColorChange}
+        onToggleAxis={this.props.onToggleAxis}
+      />
+    );
+  }
+}
+
+interface LegendValueProps {
+  value: string;
+  valueName: string;
+  asTable?: boolean;
+}
+
+function LegendValue(props: LegendValueProps) {
+  const value = props.value;
+  const valueName = props.valueName;
+  if (props.asTable) {
+    return <td className={`graph-legend-value ${valueName}`}>{value}</td>;
+  }
+  return <div className={`graph-legend-value ${valueName}`}>{value}</div>;
+}
+
+function renderLegendValues(props: LegendItemProps, series, asTable = false) {
+  const legendValueItems = [];
+  for (const valueName of LEGEND_STATS) {
+    if (props[valueName]) {
+      const valueFormatted = series.formatValue(series.stats[valueName]);
+      legendValueItems.push(
+        <LegendValue key={valueName} valueName={valueName} value={valueFormatted} asTable={asTable} />
+      );
+    }
+  }
+  return legendValueItems;
+}
+
+function getOptionSeriesCSSClasses(series, hiddenSeries) {
+  const classes = [];
+  if (series.yaxis === 2) {
+    classes.push('graph-legend-series--right-y');
+  }
+  if (hiddenSeries[series.alias] && hiddenSeries[series.alias] === true) {
+    classes.push('graph-legend-series-hidden');
+  }
+  return classes.join(' ');
+}

+ 2 - 1
public/app/plugins/panel/graph/graph.ts

@@ -22,7 +22,7 @@ import { alignYLevel } from './align_yaxes';
 import config from 'app/core/config';
 import React from 'react';
 import ReactDOM from 'react-dom';
-import { Legend, GraphLegendProps } from './Legend';
+import { Legend, GraphLegendProps } from './Legend/Legend';
 
 import { GraphCtrl } from './module';
 
@@ -98,6 +98,7 @@ class GraphElement {
       onToggleSeries: this.ctrl.toggleSeries.bind(this.ctrl),
       onToggleSort: this.ctrl.toggleSort.bind(this.ctrl),
       onColorChange: this.ctrl.changeSeriesColor.bind(this.ctrl),
+      onToggleAxis: this.ctrl.toggleAxis.bind(this.ctrl),
     };
     const legendReactElem = React.createElement(Legend, legendProps);
     const legendElem = this.elem.parent().find('.graph-legend');