Merge branch 'graph-legend-to-react'
This commit is contained in:
@@ -1,53 +1,84 @@
|
||||
import React from 'react';
|
||||
import { ColorPickerPopover } from './ColorPickerPopover';
|
||||
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Drop from 'tether-drop';
|
||||
import { SeriesColorPickerPopover } from './SeriesColorPickerPopover';
|
||||
|
||||
export interface Props {
|
||||
series: any;
|
||||
onColorChange: (color: string) => void;
|
||||
onToggleAxis: () => void;
|
||||
export interface SeriesColorPickerProps {
|
||||
color: string;
|
||||
yaxis?: number;
|
||||
optionalClass?: string;
|
||||
onColorChange: (newColor: string) => void;
|
||||
onToggleAxis?: () => void;
|
||||
}
|
||||
|
||||
export class SeriesColorPicker extends React.Component<Props, any> {
|
||||
export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
|
||||
pickerElem: any;
|
||||
colorPickerDrop: any;
|
||||
|
||||
static defaultProps = {
|
||||
optionalClass: '',
|
||||
yaxis: undefined,
|
||||
onToggleAxis: () => {},
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onColorChange = this.onColorChange.bind(this);
|
||||
this.onToggleAxis = this.onToggleAxis.bind(this);
|
||||
}
|
||||
|
||||
onColorChange(color) {
|
||||
this.props.onColorChange(color);
|
||||
componentWillUnmount() {
|
||||
this.destroyDrop();
|
||||
}
|
||||
|
||||
onToggleAxis() {
|
||||
this.props.onToggleAxis();
|
||||
}
|
||||
onClickToOpen = () => {
|
||||
if (this.colorPickerDrop) {
|
||||
this.destroyDrop();
|
||||
}
|
||||
|
||||
renderAxisSelection() {
|
||||
const leftButtonClass = this.props.series.yaxis === 1 ? 'btn-success' : 'btn-inverse';
|
||||
const rightButtonClass = this.props.series.yaxis === 2 ? 'btn-success' : 'btn-inverse';
|
||||
|
||||
return (
|
||||
<div className="p-b-1">
|
||||
<label className="small p-r-1">Y Axis:</label>
|
||||
<button onClick={this.onToggleAxis} className={'btn btn-small ' + leftButtonClass}>
|
||||
Left
|
||||
</button>
|
||||
<button onClick={this.onToggleAxis} className={'btn btn-small ' + rightButtonClass}>
|
||||
Right
|
||||
</button>
|
||||
</div>
|
||||
const { color, yaxis, onColorChange, onToggleAxis } = this.props;
|
||||
const dropContent = (
|
||||
<SeriesColorPickerPopover 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, children } = this.props;
|
||||
return (
|
||||
<div className="graph-legend-popover">
|
||||
{this.props.series.yaxis && this.renderAxisSelection()}
|
||||
<ColorPickerPopover color={this.props.series.color} onColorSelect={this.onColorChange} />
|
||||
<div className={optionalClass} ref={e => (this.pickerElem = e)} onClick={this.onClickToOpen}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
react2AngularDirective('seriesColorPicker', SeriesColorPicker, ['series', 'onColorChange', 'onToggleAxis']);
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import { ColorPickerPopover } from './ColorPickerPopover';
|
||||
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
||||
|
||||
export interface SeriesColorPickerPopoverProps {
|
||||
color: string;
|
||||
yaxis?: number;
|
||||
onColorChange: (color: string) => void;
|
||||
onToggleAxis?: () => void;
|
||||
}
|
||||
|
||||
export class SeriesColorPickerPopover extends React.PureComponent<SeriesColorPickerPopoverProps, 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;
|
||||
}
|
||||
|
||||
interface AxisSelectorState {
|
||||
yaxis: number;
|
||||
}
|
||||
|
||||
export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSelectorState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
yaxis: this.props.yaxis,
|
||||
};
|
||||
this.onToggleAxis = this.onToggleAxis.bind(this);
|
||||
}
|
||||
|
||||
onToggleAxis() {
|
||||
this.setState({
|
||||
yaxis: this.state.yaxis === 2 ? 1 : 2,
|
||||
});
|
||||
this.props.onToggleAxis();
|
||||
}
|
||||
|
||||
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">
|
||||
<label className="small p-r-1">Y Axis:</label>
|
||||
<button onClick={this.onToggleAxis} className={'btn btn-small ' + leftButtonClass}>
|
||||
Left
|
||||
</button>
|
||||
<button onClick={this.onToggleAxis} className={'btn btn-small ' + rightButtonClass}>
|
||||
Right
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopover, [
|
||||
'series',
|
||||
'onColorChange',
|
||||
'onToggleAxis',
|
||||
]);
|
||||
@@ -14,7 +14,7 @@ import './components/jsontree/jsontree';
|
||||
import './components/code_editor/code_editor';
|
||||
import './utils/outline';
|
||||
import './components/colorpicker/ColorPicker';
|
||||
import './components/colorpicker/SeriesColorPicker';
|
||||
import './components/colorpicker/SeriesColorPickerPopover';
|
||||
import './components/colorpicker/spectrum_picker';
|
||||
import './services/search_srv';
|
||||
import './services/ng_react';
|
||||
|
||||
@@ -133,6 +133,7 @@ export class PanelModel {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.events.emit('panel-teardown');
|
||||
this.events.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,11 +48,6 @@ export class PanelCtrl {
|
||||
}
|
||||
|
||||
$scope.$on('component-did-mount', () => this.panelDidMount());
|
||||
|
||||
$scope.$on('$destroy', () => {
|
||||
this.events.emit('panel-teardown');
|
||||
this.events.removeAllListeners();
|
||||
});
|
||||
}
|
||||
|
||||
panelDidMount() {
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
import _ from 'lodash';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { TimeSeries } from 'app/core/core';
|
||||
import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
|
||||
import { LegendItem, LEGEND_STATS } from './LegendSeriesItem';
|
||||
|
||||
interface LegendProps {
|
||||
seriesList: TimeSeries[];
|
||||
optionalClass?: string;
|
||||
}
|
||||
|
||||
interface LegendEventHandlers {
|
||||
onToggleSeries?: (hiddenSeries) => void;
|
||||
onToggleSort?: (sortBy, sortDesc) => void;
|
||||
onToggleAxis?: (series: TimeSeries) => void;
|
||||
onColorChange?: (series: TimeSeries, color: string) => void;
|
||||
}
|
||||
|
||||
interface LegendComponentEventHandlers {
|
||||
onToggleSeries?: (series, event) => void;
|
||||
onToggleSort?: (sortBy, sortDesc) => void;
|
||||
onToggleAxis?: (series: TimeSeries) => void;
|
||||
onColorChange?: (series: TimeSeries, color: string) => void;
|
||||
}
|
||||
|
||||
interface LegendDisplayProps {
|
||||
hiddenSeries: any;
|
||||
hideEmpty?: boolean;
|
||||
hideZero?: boolean;
|
||||
alignAsTable?: boolean;
|
||||
rightSide?: boolean;
|
||||
sideWidth?: number;
|
||||
}
|
||||
|
||||
interface LegendValuesProps {
|
||||
values?: boolean;
|
||||
min?: boolean;
|
||||
max?: boolean;
|
||||
avg?: boolean;
|
||||
current?: boolean;
|
||||
total?: boolean;
|
||||
}
|
||||
|
||||
interface LegendSortProps {
|
||||
sort?: 'min' | 'max' | 'avg' | 'current' | 'total';
|
||||
sortDesc?: boolean;
|
||||
}
|
||||
|
||||
export type GraphLegendProps = LegendProps &
|
||||
LegendDisplayProps &
|
||||
LegendValuesProps &
|
||||
LegendSortProps &
|
||||
LegendEventHandlers;
|
||||
export type LegendComponentProps = LegendProps &
|
||||
LegendDisplayProps &
|
||||
LegendValuesProps &
|
||||
LegendSortProps &
|
||||
LegendComponentEventHandlers;
|
||||
|
||||
interface LegendState {
|
||||
hiddenSeries: { [seriesAlias: string]: boolean };
|
||||
}
|
||||
|
||||
export class GraphLegend extends PureComponent<GraphLegendProps, LegendState> {
|
||||
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: () => {},
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
hiddenSeries: this.props.hiddenSeries,
|
||||
};
|
||||
}
|
||||
|
||||
sortLegend() {
|
||||
let seriesList = [...this.props.seriesList] || [];
|
||||
if (this.props.sort) {
|
||||
seriesList = _.sortBy(seriesList, series => {
|
||||
let sort = series.stats[this.props.sort];
|
||||
if (sort === null) {
|
||||
sort = -Infinity;
|
||||
}
|
||||
return sort;
|
||||
});
|
||||
if (this.props.sortDesc) {
|
||||
seriesList = seriesList.reverse();
|
||||
}
|
||||
}
|
||||
return seriesList;
|
||||
}
|
||||
|
||||
onToggleSeries = (series, event) => {
|
||||
let hiddenSeries = { ...this.state.hiddenSeries };
|
||||
if (event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||
if (hiddenSeries[series.alias]) {
|
||||
delete hiddenSeries[series.alias];
|
||||
} else {
|
||||
hiddenSeries[series.alias] = true;
|
||||
}
|
||||
} else {
|
||||
hiddenSeries = this.toggleSeriesExclusiveMode(series);
|
||||
}
|
||||
this.setState({ hiddenSeries: hiddenSeries });
|
||||
this.props.onToggleSeries(hiddenSeries);
|
||||
};
|
||||
|
||||
toggleSeriesExclusiveMode(series) {
|
||||
const hiddenSeries = { ...this.state.hiddenSeries };
|
||||
|
||||
if (hiddenSeries[series.alias]) {
|
||||
delete hiddenSeries[series.alias];
|
||||
}
|
||||
|
||||
// check if every other series is hidden
|
||||
const alreadyExclusive = this.props.seriesList.every(value => {
|
||||
if (value.alias === series.alias) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hiddenSeries[value.alias];
|
||||
});
|
||||
|
||||
if (alreadyExclusive) {
|
||||
// remove all hidden series
|
||||
this.props.seriesList.forEach(value => {
|
||||
delete hiddenSeries[value.alias];
|
||||
});
|
||||
} else {
|
||||
// hide all but this serie
|
||||
this.props.seriesList.forEach(value => {
|
||||
if (value.alias === series.alias) {
|
||||
return;
|
||||
}
|
||||
|
||||
hiddenSeries[value.alias] = true;
|
||||
});
|
||||
}
|
||||
|
||||
return hiddenSeries;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
optionalClass,
|
||||
rightSide,
|
||||
sideWidth,
|
||||
sort,
|
||||
sortDesc,
|
||||
hideEmpty,
|
||||
hideZero,
|
||||
values,
|
||||
min,
|
||||
max,
|
||||
avg,
|
||||
current,
|
||||
total,
|
||||
} = this.props;
|
||||
const seriesValuesProps = { values, min, max, avg, current, total };
|
||||
const hiddenSeries = this.state.hiddenSeries;
|
||||
const seriesHideProps = { hideEmpty, hideZero };
|
||||
const sortProps = { sort, sortDesc };
|
||||
const seriesList = this.sortLegend().filter(series => !series.hideFromLegend(seriesHideProps));
|
||||
const legendClass = `${this.props.alignAsTable ? 'graph-legend-table' : ''} ${optionalClass}`;
|
||||
|
||||
// Set min-width if side style and there is a value, otherwise remove the CSS property
|
||||
// Set width so it works with IE11
|
||||
const width: any = rightSide && sideWidth ? sideWidth : undefined;
|
||||
const ieWidth: any = rightSide && sideWidth ? sideWidth - 1 : undefined;
|
||||
const legendStyle: React.CSSProperties = {
|
||||
minWidth: width,
|
||||
width: ieWidth,
|
||||
};
|
||||
|
||||
const legendProps: LegendComponentProps = {
|
||||
seriesList: seriesList,
|
||||
hiddenSeries: hiddenSeries,
|
||||
onToggleSeries: this.onToggleSeries,
|
||||
onToggleAxis: this.props.onToggleAxis,
|
||||
onToggleSort: this.props.onToggleSort,
|
||||
onColorChange: this.props.onColorChange,
|
||||
...seriesValuesProps,
|
||||
...sortProps,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`graph-legend-content ${legendClass}`} style={legendStyle}>
|
||||
{this.props.alignAsTable ? <LegendTable {...legendProps} /> : <LegendSeriesList {...legendProps} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LegendSeriesList extends PureComponent<LegendComponentProps> {
|
||||
render() {
|
||||
const { seriesList, hiddenSeries, values, min, max, avg, current, total } = this.props;
|
||||
const seriesValuesProps = { values, min, max, avg, current, total };
|
||||
return seriesList.map((series, i) => (
|
||||
<LegendItem
|
||||
// This trick required because TimeSeries.id is not unique (it's just TimeSeries.alias).
|
||||
// In future would be good to make id unique across the series list.
|
||||
key={`${series.id}-${i}`}
|
||||
series={series}
|
||||
hidden={hiddenSeries[series.alias]}
|
||||
{...seriesValuesProps}
|
||||
onLabelClick={this.props.onToggleSeries}
|
||||
onColorChange={this.props.onColorChange}
|
||||
onToggleAxis={this.props.onToggleAxis}
|
||||
/>
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class LegendTable extends PureComponent<Partial<LegendComponentProps>> {
|
||||
onToggleSort = stat => {
|
||||
let sortDesc = this.props.sortDesc;
|
||||
let sortBy = this.props.sort;
|
||||
if (stat !== sortBy) {
|
||||
sortDesc = null;
|
||||
}
|
||||
|
||||
// if already sort ascending, disable sorting
|
||||
if (sortDesc === false) {
|
||||
sortBy = null;
|
||||
sortDesc = null;
|
||||
} else {
|
||||
sortDesc = !sortDesc;
|
||||
sortBy = stat;
|
||||
}
|
||||
this.props.onToggleSort(sortBy, sortDesc);
|
||||
};
|
||||
|
||||
render() {
|
||||
const seriesList = this.props.seriesList;
|
||||
const { values, min, max, avg, current, total, sort, sortDesc, hiddenSeries } = this.props;
|
||||
const seriesValuesProps = { values, min, max, avg, current, total };
|
||||
return (
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style={{ width: '100%' }} />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ textAlign: 'left' }} />
|
||||
{LEGEND_STATS.map(
|
||||
statName =>
|
||||
seriesValuesProps[statName] && (
|
||||
<LegendTableHeaderItem
|
||||
key={statName}
|
||||
statName={statName}
|
||||
sort={sort}
|
||||
sortDesc={sortDesc}
|
||||
onClick={this.onToggleSort}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{seriesList.map((series, i) => (
|
||||
<LegendItem
|
||||
key={`${series.id}-${i}`}
|
||||
asTable={true}
|
||||
series={series}
|
||||
hidden={hiddenSeries[series.alias]}
|
||||
onLabelClick={this.props.onToggleSeries}
|
||||
onColorChange={this.props.onColorChange}
|
||||
onToggleAxis={this.props.onToggleAxis}
|
||||
{...seriesValuesProps}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface LegendTableHeaderProps {
|
||||
statName: string;
|
||||
onClick?: (statName: string) => void;
|
||||
}
|
||||
|
||||
class LegendTableHeaderItem extends PureComponent<LegendTableHeaderProps & LegendSortProps> {
|
||||
onClick = () => this.props.onClick(this.props.statName);
|
||||
|
||||
render() {
|
||||
const { statName, sort, sortDesc } = this.props;
|
||||
return (
|
||||
<th className="pointer" onClick={this.onClick}>
|
||||
{statName}
|
||||
{sort === statName && <span className={sortDesc ? 'fa fa-caret-down' : 'fa fa-caret-up'} />}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Legend extends PureComponent<GraphLegendProps> {
|
||||
render() {
|
||||
return (
|
||||
<CustomScrollbar>
|
||||
<GraphLegend {...this.props} />
|
||||
</CustomScrollbar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Legend;
|
||||
@@ -0,0 +1,196 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { TimeSeries } from 'app/core/core';
|
||||
import { SeriesColorPicker } from 'app/core/components/colorpicker/SeriesColorPicker';
|
||||
|
||||
export const LEGEND_STATS = ['min', 'max', 'avg', 'current', 'total'];
|
||||
|
||||
export interface LegendLabelProps {
|
||||
series: TimeSeries;
|
||||
asTable?: boolean;
|
||||
hidden?: boolean;
|
||||
onLabelClick?: (series, event) => void;
|
||||
onColorChange?: (series, color: string) => void;
|
||||
onToggleAxis?: (series) => void;
|
||||
}
|
||||
|
||||
export interface LegendValuesProps {
|
||||
values?: boolean;
|
||||
min?: boolean;
|
||||
max?: boolean;
|
||||
avg?: boolean;
|
||||
current?: boolean;
|
||||
total?: boolean;
|
||||
}
|
||||
|
||||
type LegendItemProps = LegendLabelProps & LegendValuesProps;
|
||||
|
||||
interface LegendItemState {
|
||||
yaxis: number;
|
||||
}
|
||||
|
||||
export class LegendItem extends PureComponent<LegendItemProps, LegendItemState> {
|
||||
static defaultProps = {
|
||||
asTable: false,
|
||||
hidden: false,
|
||||
onLabelClick: () => {},
|
||||
onColorChange: () => {},
|
||||
onToggleAxis: () => {},
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
yaxis: this.props.series.yaxis,
|
||||
};
|
||||
}
|
||||
|
||||
onLabelClick = e => this.props.onLabelClick(this.props.series, e);
|
||||
|
||||
onToggleAxis = () => {
|
||||
const yaxis = this.state.yaxis === 2 ? 1 : 2;
|
||||
const info = { alias: this.props.series.alias, yaxis: yaxis };
|
||||
this.setState({ yaxis: yaxis });
|
||||
this.props.onToggleAxis(info);
|
||||
};
|
||||
|
||||
onColorChange = color => {
|
||||
this.props.onColorChange(this.props.series, color);
|
||||
// Because of PureComponent nature it makes only shallow props comparison and changing of series.color doesn't run
|
||||
// component re-render. In this case we can't rely on color, selected by user, because it may be overwritten
|
||||
// by series overrides. So we need to use forceUpdate() to make sure we have proper series color.
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
renderLegendValues() {
|
||||
const { series, asTable } = this.props;
|
||||
const legendValueItems = [];
|
||||
for (const valueName of LEGEND_STATS) {
|
||||
if (this.props[valueName]) {
|
||||
const valueFormatted = series.formatValue(series.stats[valueName]);
|
||||
legendValueItems.push(
|
||||
<LegendValue key={valueName} valueName={valueName} value={valueFormatted} asTable={asTable} />
|
||||
);
|
||||
}
|
||||
}
|
||||
return legendValueItems;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { series, values, asTable, hidden } = this.props;
|
||||
const seriesOptionClasses = classNames({
|
||||
'graph-legend-series-hidden': hidden,
|
||||
'graph-legend-series--right-y': series.yaxis === 2,
|
||||
});
|
||||
const valueItems = values ? this.renderLegendValues() : [];
|
||||
const seriesLabel = (
|
||||
<LegendSeriesLabel
|
||||
label={series.alias}
|
||||
color={series.color}
|
||||
yaxis={this.state.yaxis}
|
||||
onLabelClick={this.onLabelClick}
|
||||
onColorChange={this.onColorChange}
|
||||
onToggleAxis={this.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 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;
|
||||
}
|
||||
|
||||
interface LegendSeriesIconState {
|
||||
color: string;
|
||||
}
|
||||
|
||||
function SeriesIcon(props) {
|
||||
return <i className="fa fa-minus pointer" style={{ color: props.color }} />;
|
||||
}
|
||||
|
||||
class LegendSeriesIcon extends PureComponent<LegendSeriesIconProps, LegendSeriesIconState> {
|
||||
static defaultProps = {
|
||||
yaxis: undefined,
|
||||
onColorChange: () => {},
|
||||
onToggleAxis: () => {},
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SeriesColorPicker
|
||||
optionalClass="graph-legend-icon"
|
||||
yaxis={this.props.yaxis}
|
||||
color={this.props.color}
|
||||
onColorChange={this.props.onColorChange}
|
||||
onToggleAxis={this.props.onToggleAxis}
|
||||
>
|
||||
<SeriesIcon color={this.props.color} />
|
||||
</SeriesColorPicker>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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>;
|
||||
}
|
||||
@@ -20,6 +20,9 @@ import { EventManager } from 'app/features/annotations/all';
|
||||
import { convertToHistogramData } from './histogram';
|
||||
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/Legend';
|
||||
|
||||
import { GraphCtrl } from './module';
|
||||
|
||||
@@ -35,6 +38,7 @@ class GraphElement {
|
||||
panelWidth: number;
|
||||
eventManager: EventManager;
|
||||
thresholdManager: ThresholdManager;
|
||||
legendElem: HTMLElement;
|
||||
|
||||
constructor(private scope, private elem, private timeSrv) {
|
||||
this.ctrl = scope.ctrl;
|
||||
@@ -50,7 +54,7 @@ class GraphElement {
|
||||
});
|
||||
|
||||
// panel events
|
||||
this.ctrl.events.on('panel-teardown', this.onPanelteardown.bind(this));
|
||||
this.ctrl.events.on('panel-teardown', this.onPanelTeardown.bind(this));
|
||||
|
||||
/**
|
||||
* Split graph rendering into two parts.
|
||||
@@ -63,13 +67,14 @@ class GraphElement {
|
||||
|
||||
// global events
|
||||
appEvents.on('graph-hover', this.onGraphHover.bind(this), scope);
|
||||
|
||||
appEvents.on('graph-hover-clear', this.onGraphHoverClear.bind(this), scope);
|
||||
|
||||
this.elem.bind('plotselected', this.onPlotSelected.bind(this));
|
||||
|
||||
this.elem.bind('plotclick', this.onPlotClick.bind(this));
|
||||
scope.$on('$destroy', this.onScopeDestroy.bind(this));
|
||||
|
||||
// get graph legend element
|
||||
if (this.elem && this.elem.parent) {
|
||||
this.legendElem = this.elem.parent().find('.graph-legend')[0];
|
||||
}
|
||||
}
|
||||
|
||||
onRender(renderData) {
|
||||
@@ -82,7 +87,26 @@ class GraphElement {
|
||||
const graphHeight = this.elem.height();
|
||||
updateLegendValues(this.data, this.panel, graphHeight);
|
||||
|
||||
this.ctrl.events.emit('render-legend');
|
||||
const { values, min, max, avg, current, total } = this.panel.legend;
|
||||
const { alignAsTable, rightSide, sideWidth, sort, sortDesc, hideEmpty, hideZero } = this.panel.legend;
|
||||
const legendOptions = { alignAsTable, rightSide, sideWidth, sort, sortDesc, hideEmpty, hideZero };
|
||||
const valueOptions = { values, min, max, avg, current, total };
|
||||
const legendProps: GraphLegendProps = {
|
||||
seriesList: this.data,
|
||||
hiddenSeries: this.ctrl.hiddenSeries,
|
||||
...legendOptions,
|
||||
...valueOptions,
|
||||
onToggleSeries: this.ctrl.onToggleSeries,
|
||||
onToggleSort: this.ctrl.onToggleSort,
|
||||
onColorChange: this.ctrl.onColorChange,
|
||||
onToggleAxis: this.ctrl.onToggleAxis,
|
||||
};
|
||||
const legendReactElem = React.createElement(Legend, legendProps);
|
||||
ReactDOM.render(legendReactElem, this.legendElem, () => this.onLegendRenderingComplete());
|
||||
}
|
||||
|
||||
onLegendRenderingComplete() {
|
||||
this.render_panel();
|
||||
}
|
||||
|
||||
onGraphHover(evt) {
|
||||
@@ -99,17 +123,20 @@ class GraphElement {
|
||||
this.tooltip.show(evt.pos);
|
||||
}
|
||||
|
||||
onPanelteardown() {
|
||||
onPanelTeardown() {
|
||||
this.thresholdManager = null;
|
||||
|
||||
if (this.plot) {
|
||||
this.plot.destroy();
|
||||
this.plot = null;
|
||||
}
|
||||
}
|
||||
|
||||
onLegendRenderingComplete() {
|
||||
this.render_panel();
|
||||
this.tooltip.destroy();
|
||||
this.elem.off();
|
||||
this.elem.remove();
|
||||
|
||||
console.log('react unmount');
|
||||
ReactDOM.unmountComponentAtNode(this.legendElem);
|
||||
}
|
||||
|
||||
onGraphHoverClear(event, info) {
|
||||
@@ -157,12 +184,6 @@ class GraphElement {
|
||||
}
|
||||
}
|
||||
|
||||
onScopeDestroy() {
|
||||
this.tooltip.destroy();
|
||||
this.elem.off();
|
||||
this.elem.remove();
|
||||
}
|
||||
|
||||
shouldAbortRender() {
|
||||
if (!this.data) {
|
||||
return true;
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import baron from 'baron';
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
/** @ngInject */
|
||||
function graphLegendDirective(popoverSrv, $timeout) {
|
||||
return {
|
||||
link: (scope, elem) => {
|
||||
let firstRender = true;
|
||||
const ctrl = scope.ctrl;
|
||||
const panel = ctrl.panel;
|
||||
let data;
|
||||
let seriesList;
|
||||
let i;
|
||||
let legendScrollbar;
|
||||
const legendRightDefaultWidth = 10;
|
||||
const legendElem = elem.parent();
|
||||
|
||||
scope.$on('$destroy', () => {
|
||||
destroyScrollbar();
|
||||
});
|
||||
|
||||
ctrl.events.on('render-legend', () => {
|
||||
data = ctrl.seriesList;
|
||||
if (data) {
|
||||
render();
|
||||
}
|
||||
ctrl.events.emit('legend-rendering-complete');
|
||||
});
|
||||
|
||||
function getSeriesIndexForElement(el) {
|
||||
return el.parents('[data-series-index]').data('series-index');
|
||||
}
|
||||
|
||||
function openColorSelector(e) {
|
||||
// if we clicked inside poup container ignore click
|
||||
if ($(e.target).parents('.popover').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = $(e.currentTarget).find('.fa-minus');
|
||||
const index = getSeriesIndexForElement(el);
|
||||
const series = seriesList[index];
|
||||
|
||||
$timeout(() => {
|
||||
popoverSrv.show({
|
||||
element: el[0],
|
||||
position: 'bottom left',
|
||||
targetAttachment: 'top left',
|
||||
template:
|
||||
'<series-color-picker series="series" onToggleAxis="toggleAxis" onColorChange="colorSelected">' +
|
||||
'</series-color-picker>',
|
||||
openOn: 'hover',
|
||||
model: {
|
||||
series: series,
|
||||
toggleAxis: () => {
|
||||
ctrl.toggleAxis(series);
|
||||
},
|
||||
colorSelected: color => {
|
||||
ctrl.changeSeriesColor(series, color);
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toggleSeries(e) {
|
||||
const el = $(e.currentTarget);
|
||||
const index = getSeriesIndexForElement(el);
|
||||
const seriesInfo = seriesList[index];
|
||||
const scrollPosition = legendScrollbar.scroller.scrollTop;
|
||||
ctrl.toggleSeries(seriesInfo, e);
|
||||
legendScrollbar.scroller.scrollTop = scrollPosition;
|
||||
}
|
||||
|
||||
function sortLegend(e) {
|
||||
const el = $(e.currentTarget);
|
||||
const stat = el.data('stat');
|
||||
|
||||
if (stat !== panel.legend.sort) {
|
||||
panel.legend.sortDesc = null;
|
||||
}
|
||||
|
||||
// if already sort ascending, disable sorting
|
||||
if (panel.legend.sortDesc === false) {
|
||||
panel.legend.sort = null;
|
||||
panel.legend.sortDesc = null;
|
||||
ctrl.render();
|
||||
return;
|
||||
}
|
||||
|
||||
panel.legend.sortDesc = !panel.legend.sortDesc;
|
||||
panel.legend.sort = stat;
|
||||
ctrl.render();
|
||||
}
|
||||
|
||||
function getTableHeaderHtml(statName) {
|
||||
if (!panel.legend[statName]) {
|
||||
return '';
|
||||
}
|
||||
let html = '<th class="pointer" data-stat="' + statName + '">' + statName;
|
||||
|
||||
if (panel.legend.sort === statName) {
|
||||
const cssClass = panel.legend.sortDesc ? 'fa fa-caret-down' : 'fa fa-caret-up';
|
||||
html += ' <span class="' + cssClass + '"></span>';
|
||||
}
|
||||
|
||||
return html + '</th>';
|
||||
}
|
||||
|
||||
function render() {
|
||||
const legendWidth = legendElem.width();
|
||||
if (!ctrl.panel.legend.show) {
|
||||
elem.empty();
|
||||
firstRender = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstRender) {
|
||||
elem.on('click', '.graph-legend-icon', openColorSelector);
|
||||
elem.on('click', '.graph-legend-alias', toggleSeries);
|
||||
elem.on('click', 'th', sortLegend);
|
||||
firstRender = false;
|
||||
}
|
||||
|
||||
seriesList = data;
|
||||
|
||||
elem.empty();
|
||||
|
||||
// Set min-width if side style and there is a value, otherwise remove the CSS property
|
||||
// Set width so it works with IE11
|
||||
const width: any = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth + 'px' : '';
|
||||
const ieWidth: any = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth - 1 + 'px' : '';
|
||||
legendElem.css('min-width', width);
|
||||
legendElem.css('width', ieWidth);
|
||||
|
||||
elem.toggleClass('graph-legend-table', panel.legend.alignAsTable === true);
|
||||
|
||||
let tableHeaderElem;
|
||||
if (panel.legend.alignAsTable) {
|
||||
let header = '<tr>';
|
||||
header += '<th colspan="2" style="text-align:left"></th>';
|
||||
if (panel.legend.values) {
|
||||
header += getTableHeaderHtml('min');
|
||||
header += getTableHeaderHtml('max');
|
||||
header += getTableHeaderHtml('avg');
|
||||
header += getTableHeaderHtml('current');
|
||||
header += getTableHeaderHtml('total');
|
||||
}
|
||||
header += '</tr>';
|
||||
tableHeaderElem = $(header);
|
||||
}
|
||||
|
||||
if (panel.legend.sort) {
|
||||
seriesList = _.sortBy(seriesList, series => {
|
||||
let sort = series.stats[panel.legend.sort];
|
||||
if (sort === null) {
|
||||
sort = -Infinity;
|
||||
}
|
||||
return sort;
|
||||
});
|
||||
if (panel.legend.sortDesc) {
|
||||
seriesList = seriesList.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
// render first time for getting proper legend height
|
||||
if (!panel.legend.rightSide || (panel.legend.rightSide && legendWidth !== legendRightDefaultWidth)) {
|
||||
renderLegendElement(tableHeaderElem);
|
||||
elem.empty();
|
||||
}
|
||||
|
||||
renderLegendElement(tableHeaderElem);
|
||||
}
|
||||
|
||||
function renderSeriesLegendElements() {
|
||||
const seriesElements = [];
|
||||
for (i = 0; i < seriesList.length; i++) {
|
||||
const series = seriesList[i];
|
||||
|
||||
if (series.hideFromLegend(panel.legend)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let html = '<div class="graph-legend-series';
|
||||
|
||||
if (series.yaxis === 2) {
|
||||
html += ' graph-legend-series--right-y';
|
||||
}
|
||||
if (ctrl.hiddenSeries[series.alias]) {
|
||||
html += ' graph-legend-series-hidden';
|
||||
}
|
||||
html += '" data-series-index="' + i + '">';
|
||||
html += '<div class="graph-legend-icon">';
|
||||
html += '<i class="fa fa-minus pointer" style="color:' + series.color + '"></i>';
|
||||
html += '</div>';
|
||||
|
||||
html +=
|
||||
'<a class="graph-legend-alias pointer" title="' + series.aliasEscaped + '">' + series.aliasEscaped + '</a>';
|
||||
|
||||
if (panel.legend.values) {
|
||||
const avg = series.formatValue(series.stats.avg);
|
||||
const current = series.formatValue(series.stats.current);
|
||||
const min = series.formatValue(series.stats.min);
|
||||
const max = series.formatValue(series.stats.max);
|
||||
const total = series.formatValue(series.stats.total);
|
||||
|
||||
if (panel.legend.min) {
|
||||
html += '<div class="graph-legend-value min">' + min + '</div>';
|
||||
}
|
||||
if (panel.legend.max) {
|
||||
html += '<div class="graph-legend-value max">' + max + '</div>';
|
||||
}
|
||||
if (panel.legend.avg) {
|
||||
html += '<div class="graph-legend-value avg">' + avg + '</div>';
|
||||
}
|
||||
if (panel.legend.current) {
|
||||
html += '<div class="graph-legend-value current">' + current + '</div>';
|
||||
}
|
||||
if (panel.legend.total) {
|
||||
html += '<div class="graph-legend-value total">' + total + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
seriesElements.push($(html));
|
||||
}
|
||||
return seriesElements;
|
||||
}
|
||||
|
||||
function renderLegendElement(tableHeaderElem) {
|
||||
const legendWidth = elem.width();
|
||||
|
||||
const seriesElements = renderSeriesLegendElements();
|
||||
|
||||
if (panel.legend.alignAsTable) {
|
||||
const tbodyElem = $('<tbody></tbody>');
|
||||
tbodyElem.append(tableHeaderElem);
|
||||
tbodyElem.append(seriesElements);
|
||||
elem.append(tbodyElem);
|
||||
tbodyElem.wrap('<div class="graph-legend-scroll"></div>');
|
||||
} else {
|
||||
elem.append('<div class="graph-legend-scroll"></div>');
|
||||
elem.find('.graph-legend-scroll').append(seriesElements);
|
||||
}
|
||||
|
||||
if (!panel.legend.rightSide || (panel.legend.rightSide && legendWidth !== legendRightDefaultWidth)) {
|
||||
addScrollbar();
|
||||
} else {
|
||||
destroyScrollbar();
|
||||
}
|
||||
}
|
||||
|
||||
function addScrollbar() {
|
||||
const scrollRootClass = 'baron baron__root';
|
||||
const scrollerClass = 'baron__scroller';
|
||||
const scrollBarHTML = `
|
||||
<div class="baron__track">
|
||||
<div class="baron__bar"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const scrollRoot = elem;
|
||||
const scroller = elem.find('.graph-legend-scroll');
|
||||
|
||||
// clear existing scroll bar track to prevent duplication
|
||||
scrollRoot.find('.baron__track').remove();
|
||||
|
||||
scrollRoot.addClass(scrollRootClass);
|
||||
$(scrollBarHTML).appendTo(scrollRoot);
|
||||
scroller.addClass(scrollerClass);
|
||||
|
||||
const scrollbarParams = {
|
||||
root: scrollRoot[0],
|
||||
scroller: scroller[0],
|
||||
bar: '.baron__bar',
|
||||
track: '.baron__track',
|
||||
barOnCls: '_scrollbar',
|
||||
scrollingCls: '_scrolling',
|
||||
};
|
||||
|
||||
if (!legendScrollbar) {
|
||||
legendScrollbar = baron(scrollbarParams);
|
||||
} else {
|
||||
destroyScrollbar();
|
||||
legendScrollbar = baron(scrollbarParams);
|
||||
}
|
||||
|
||||
// #11830 - compensates for Firefox scrollbar calculation error in the baron framework
|
||||
scroller[0].style.marginRight = '-' + (scroller[0].offsetWidth - scroller[0].clientWidth) + 'px';
|
||||
|
||||
legendScrollbar.scroll();
|
||||
}
|
||||
|
||||
function destroyScrollbar() {
|
||||
if (legendScrollbar) {
|
||||
legendScrollbar.dispose();
|
||||
legendScrollbar = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('graphLegend', graphLegendDirective);
|
||||
@@ -1,5 +1,4 @@
|
||||
import './graph';
|
||||
import './legend';
|
||||
import './series_overrides_ctrl';
|
||||
import './thresholds_form';
|
||||
|
||||
@@ -244,67 +243,32 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
changeSeriesColor(series, color) {
|
||||
onColorChange = (series, color) => {
|
||||
series.setColor(color);
|
||||
this.panel.aliasColors[series.alias] = series.color;
|
||||
this.render();
|
||||
}
|
||||
};
|
||||
|
||||
toggleSeries(serie, event) {
|
||||
if (event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||
if (this.hiddenSeries[serie.alias]) {
|
||||
delete this.hiddenSeries[serie.alias];
|
||||
} else {
|
||||
this.hiddenSeries[serie.alias] = true;
|
||||
}
|
||||
} else {
|
||||
this.toggleSeriesExclusiveMode(serie);
|
||||
}
|
||||
onToggleSeries = hiddenSeries => {
|
||||
this.hiddenSeries = hiddenSeries;
|
||||
this.render();
|
||||
}
|
||||
};
|
||||
|
||||
toggleSeriesExclusiveMode(serie) {
|
||||
const hidden = this.hiddenSeries;
|
||||
onToggleSort = (sortBy, sortDesc) => {
|
||||
this.panel.legend.sort = sortBy;
|
||||
this.panel.legend.sortDesc = sortDesc;
|
||||
this.render();
|
||||
};
|
||||
|
||||
if (hidden[serie.alias]) {
|
||||
delete hidden[serie.alias];
|
||||
}
|
||||
|
||||
// check if every other series is hidden
|
||||
const alreadyExclusive = _.every(this.seriesList, value => {
|
||||
if (value.alias === serie.alias) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hidden[value.alias];
|
||||
});
|
||||
|
||||
if (alreadyExclusive) {
|
||||
// remove all hidden series
|
||||
_.each(this.seriesList, value => {
|
||||
delete this.hiddenSeries[value.alias];
|
||||
});
|
||||
} else {
|
||||
// hide all but this serie
|
||||
_.each(this.seriesList, value => {
|
||||
if (value.alias === serie.alias) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hiddenSeries[value.alias] = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
toggleAxis(info) {
|
||||
onToggleAxis = info => {
|
||||
let override = _.find(this.panel.seriesOverrides, { alias: info.alias });
|
||||
if (!override) {
|
||||
override = { alias: info.alias };
|
||||
this.panel.seriesOverrides.push(override);
|
||||
}
|
||||
info.yaxis = override.yaxis = info.yaxis === 2 ? 1 : 2;
|
||||
override.yaxis = info.yaxis;
|
||||
this.render();
|
||||
}
|
||||
};
|
||||
|
||||
addSeriesOverride(override) {
|
||||
this.panel.seriesOverrides.push(override || {});
|
||||
|
||||
@@ -53,7 +53,7 @@ export function SeriesOverridesCtrl($scope, $element, popoverSrv) {
|
||||
element: $element.find('.dropdown')[0],
|
||||
position: 'top center',
|
||||
openOn: 'click',
|
||||
template: '<series-color-picker series="series" onColorChange="colorSelected" />',
|
||||
template: '<series-color-picker-popover series="series" onColorChange="colorSelected" />',
|
||||
model: {
|
||||
autoClose: true,
|
||||
colorSelected: $scope.colorSelected,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
.graph-legend-series {
|
||||
display: block;
|
||||
padding-left: 0px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.graph-legend-table .graph-legend-series {
|
||||
@@ -52,9 +52,6 @@
|
||||
padding-top: 6px;
|
||||
position: relative;
|
||||
|
||||
// fix for Firefox (white stripe on the right of scrollbar)
|
||||
width: calc(100% - 1px);
|
||||
|
||||
.popover-content {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -62,15 +59,6 @@
|
||||
|
||||
.graph-legend-content {
|
||||
position: relative;
|
||||
|
||||
// fix for Firefox (white stripe on the right of scrollbar)
|
||||
width: calc(100% - 1px);
|
||||
}
|
||||
|
||||
.graph-legend-scroll {
|
||||
position: relative;
|
||||
overflow: auto !important;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.graph-legend-icon {
|
||||
@@ -82,8 +70,8 @@
|
||||
.graph-legend-icon,
|
||||
.graph-legend-alias,
|
||||
.graph-legend-value {
|
||||
display: inline;
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
white-space: nowrap;
|
||||
font-size: 85%;
|
||||
text-align: left;
|
||||
@@ -120,6 +108,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Don't move series to the right if legend is on the right as well
|
||||
.graph-panel--legend-right .graph-legend-series--right-y {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.graph-legend-value {
|
||||
padding-left: 6px;
|
||||
}
|
||||
@@ -128,7 +121,8 @@
|
||||
.body--phantomjs {
|
||||
.graph-panel--legend-right {
|
||||
.graph-legend {
|
||||
display: inline-block;
|
||||
display: block;
|
||||
max-width: min-content;
|
||||
}
|
||||
|
||||
.graph-panel__chart {
|
||||
@@ -138,24 +132,14 @@
|
||||
.graph-legend-table {
|
||||
display: table;
|
||||
width: auto;
|
||||
|
||||
.graph-legend-scroll {
|
||||
display: table;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.graph-legend-table {
|
||||
tbody {
|
||||
display: block;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 1px;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
padding-bottom: 1px;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
|
||||
.graph-legend-series {
|
||||
display: table-row;
|
||||
|
||||
Reference in New Issue
Block a user