mirror of
https://github.com/grafana/grafana.git
synced 2025-09-19 00:01:03 +08:00
Legend/GraphNG: Refactoring legend types and options (#29067)
* Legend/GraphNG: Refactoring legend types and options * Rename label * Minor update * Fixed legend placement crash issue * remove unused * Minor tweaks and fixes
This commit is contained in:
@ -44,7 +44,7 @@ export const GraphLegendListItem: React.FunctionComponent<GraphLegendItemProps>
|
||||
onLabelClick(item, event);
|
||||
}
|
||||
}}
|
||||
className={cx(styles.label, !item.isVisible && styles.labelDisabled)}
|
||||
className={cx(styles.label, item.disabled && styles.labelDisabled)}
|
||||
>
|
||||
{item.label}
|
||||
</div>
|
||||
|
@ -123,8 +123,7 @@ export const graphWithLegend = () => {
|
||||
}
|
||||
return s;
|
||||
}),
|
||||
displayMode: renderLegendAsTable ? LegendDisplayMode.Table : LegendDisplayMode.List,
|
||||
isLegendVisible: true,
|
||||
legendDisplayMode: renderLegendAsTable ? LegendDisplayMode.Table : LegendDisplayMode.List,
|
||||
onToggleSort: () => {},
|
||||
timeRange: {
|
||||
from: dateTime(1546372800000),
|
||||
|
@ -15,8 +15,7 @@ export type SeriesColorChangeHandler = SeriesOptionChangeHandler<string>;
|
||||
export type SeriesAxisToggleHandler = SeriesOptionChangeHandler<number>;
|
||||
|
||||
export interface GraphWithLegendProps extends GraphProps, LegendRenderOptions {
|
||||
isLegendVisible: boolean;
|
||||
displayMode: LegendDisplayMode;
|
||||
legendDisplayMode: LegendDisplayMode;
|
||||
sortLegendBy?: string;
|
||||
sortLegendDesc?: boolean;
|
||||
onSeriesColorChange?: SeriesColorChangeHandler;
|
||||
@ -59,8 +58,7 @@ export const GraphWithLegend: React.FunctionComponent<GraphWithLegendProps> = (p
|
||||
showPoints,
|
||||
sortLegendBy,
|
||||
sortLegendDesc,
|
||||
isLegendVisible,
|
||||
displayMode,
|
||||
legendDisplayMode,
|
||||
placement,
|
||||
onSeriesAxisToggle,
|
||||
onSeriesColorChange,
|
||||
@ -84,7 +82,7 @@ export const GraphWithLegend: React.FunctionComponent<GraphWithLegendProps> = (p
|
||||
{
|
||||
label: s.label,
|
||||
color: s.color || '',
|
||||
isVisible: s.isVisible,
|
||||
disabled: !s.isVisible,
|
||||
yAxis: s.yAxis.index,
|
||||
displayValues: s.info || [],
|
||||
},
|
||||
@ -103,7 +101,6 @@ export const GraphWithLegend: React.FunctionComponent<GraphWithLegendProps> = (p
|
||||
showBars={showBars}
|
||||
width={width}
|
||||
height={height}
|
||||
key={isLegendVisible ? 'legend-visible' : 'legend-invisible'}
|
||||
isStacked={isStacked}
|
||||
lineWidth={lineWidth}
|
||||
onHorizontalRegionSelected={onHorizontalRegionSelected}
|
||||
@ -112,12 +109,12 @@ export const GraphWithLegend: React.FunctionComponent<GraphWithLegendProps> = (p
|
||||
</Graph>
|
||||
</div>
|
||||
|
||||
{isLegendVisible && (
|
||||
{legendDisplayMode !== LegendDisplayMode.Hidden && (
|
||||
<div className={legendContainer}>
|
||||
<CustomScrollbar hideHorizontalTrack>
|
||||
<GraphLegend
|
||||
items={legendItems}
|
||||
displayMode={displayMode}
|
||||
displayMode={legendDisplayMode}
|
||||
placement={placement}
|
||||
sortBy={sortLegendBy}
|
||||
sortDesc={sortLegendDesc}
|
||||
|
@ -6,6 +6,7 @@ import { dateTime } from '@grafana/data';
|
||||
import { LegendDisplayMode } from '../Legend/Legend';
|
||||
import { prepDataForStorybook } from '../../utils/storybook/data';
|
||||
import { useTheme } from '../../themes';
|
||||
import { text, select } from '@storybook/addon-knobs';
|
||||
|
||||
export default {
|
||||
title: 'Visualizations/GraphNG',
|
||||
@ -16,7 +17,23 @@ export default {
|
||||
},
|
||||
};
|
||||
|
||||
const getKnobs = () => {
|
||||
return {
|
||||
unit: text('Unit', 'short'),
|
||||
legendPlacement: select(
|
||||
'Legend placement',
|
||||
{
|
||||
bottom: 'bottom',
|
||||
right: 'right',
|
||||
},
|
||||
'bottom'
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const Lines: React.FC = () => {
|
||||
const { unit, legendPlacement } = getKnobs();
|
||||
|
||||
const theme = useTheme();
|
||||
const seriesA = toDataFrame({
|
||||
target: 'SeriesA',
|
||||
@ -29,7 +46,7 @@ export const Lines: React.FC = () => {
|
||||
|
||||
seriesA.fields[1].config.custom = { line: { show: true } };
|
||||
seriesA.fields[1].config.color = { mode: FieldColorModeId.PaletteClassic };
|
||||
seriesA.fields[1].config.unit = 'degree';
|
||||
seriesA.fields[1].config.unit = unit;
|
||||
|
||||
const data = prepDataForStorybook([seriesA], theme);
|
||||
|
||||
@ -46,7 +63,7 @@ export const Lines: React.FC = () => {
|
||||
to: dateTime(1546380000000),
|
||||
},
|
||||
}}
|
||||
legend={{ isVisible: true, displayMode: LegendDisplayMode.List, placement: 'bottom' }}
|
||||
legend={{ displayMode: LegendDisplayMode.List, placement: legendPlacement }}
|
||||
timeZone="browser"
|
||||
></GraphNG>
|
||||
);
|
||||
|
@ -35,7 +35,6 @@ const mockData = () => {
|
||||
};
|
||||
|
||||
const defaultLegendOptions: LegendOptions = {
|
||||
isVisible: false,
|
||||
displayMode: LegendDisplayMode.List,
|
||||
placement: 'bottom',
|
||||
};
|
||||
|
@ -15,7 +15,7 @@ import { UPlotChart } from '../uPlot/Plot';
|
||||
import { AxisSide, GraphCustomFieldConfig, PlotProps } from '../uPlot/types';
|
||||
import { useTheme } from '../../themes';
|
||||
import { VizLayout } from '../VizLayout/VizLayout';
|
||||
import { LegendItem, LegendOptions } from '../Legend/Legend';
|
||||
import { LegendDisplayMode, LegendItem, LegendOptions } from '../Legend/Legend';
|
||||
import { GraphLegend } from '../Graph/GraphLegend';
|
||||
|
||||
const defaultFormatter = (v: any) => (v == null ? '-' : v.toFixed(1));
|
||||
@ -63,6 +63,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
|
||||
let seriesIdx = 0;
|
||||
const legendItems: LegendItem[] = [];
|
||||
const uniqueScales: Record<string, boolean> = {};
|
||||
const hasLegend = legend && legend.displayMode !== LegendDisplayMode.Hidden;
|
||||
|
||||
for (let i = 0; i < alignedData.fields.length; i++) {
|
||||
const seriesGeometry = [];
|
||||
@ -132,11 +133,10 @@ export const GraphNG: React.FC<GraphNGProps> = ({
|
||||
geometries.push(seriesGeometry);
|
||||
}
|
||||
|
||||
if (legend?.isVisible) {
|
||||
if (hasLegend) {
|
||||
legendItems.push({
|
||||
color: seriesColor,
|
||||
label: getFieldDisplayName(field, alignedData),
|
||||
isVisible: true,
|
||||
yAxis: customConfig?.axis?.side === 1 ? 3 : 1,
|
||||
});
|
||||
}
|
||||
@ -146,10 +146,10 @@ export const GraphNG: React.FC<GraphNGProps> = ({
|
||||
|
||||
let legendElement: React.ReactElement | undefined;
|
||||
|
||||
if (legend?.isVisible && legendItems.length > 0) {
|
||||
if (hasLegend && legendItems.length > 0) {
|
||||
legendElement = (
|
||||
<VizLayout.Legend position={legend.placement} maxHeight="35%" maxWidth="60%">
|
||||
<GraphLegend placement={legend.placement} items={legendItems} displayMode={legend.displayMode} />
|
||||
<VizLayout.Legend position={legend!.placement} maxHeight="35%" maxWidth="60%">
|
||||
<GraphLegend placement={legend!.placement} items={legendItems} displayMode={legend!.displayMode} />
|
||||
</VizLayout.Legend>
|
||||
);
|
||||
}
|
||||
|
@ -19,8 +19,8 @@ const getStoriesKnobs = (table = false) => {
|
||||
|
||||
const rawRenderer = (item: LegendItem) => (
|
||||
<>
|
||||
Label: <strong>{item.label}</strong>, Color: <strong>{item.color}</strong>, isVisible:{' '}
|
||||
<strong>{item.isVisible ? 'yes' : 'no'}</strong>
|
||||
Label: <strong>{item.label}</strong>, Color: <strong>{item.color}</strong>, disabled:{' '}
|
||||
<strong>{item.disabled ? 'yes' : 'no'}</strong>
|
||||
</>
|
||||
);
|
||||
|
||||
|
@ -11,7 +11,6 @@ export const generateLegendItems = (numberOfSeries: number, statsToDisplay?: Dis
|
||||
return {
|
||||
label: `${alphabet[i].toUpperCase()}-series`,
|
||||
color: tinycolor.fromRatio({ h: i / alphabet.length, s: 1, v: 1 }).toHexString(),
|
||||
isVisible: true,
|
||||
yAxis: 1,
|
||||
displayValues: statsToDisplay || [],
|
||||
};
|
||||
@ -21,9 +20,9 @@ export const generateLegendItems = (numberOfSeries: number, statsToDisplay?: Dis
|
||||
export enum LegendDisplayMode {
|
||||
List = 'list',
|
||||
Table = 'table',
|
||||
Hidden = 'hidden',
|
||||
}
|
||||
export interface LegendBasicOptions {
|
||||
isVisible: boolean;
|
||||
displayMode: LegendDisplayMode;
|
||||
}
|
||||
|
||||
@ -40,8 +39,8 @@ export interface LegendOptions extends LegendBasicOptions, LegendRenderOptions {
|
||||
export interface LegendItem {
|
||||
label: string;
|
||||
color: string;
|
||||
isVisible: boolean;
|
||||
yAxis: number;
|
||||
disabled?: boolean;
|
||||
displayValues?: DisplayValue[];
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ export const BottomLegend = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<VizLayout width={400} height={500} legend={legend}>
|
||||
<VizLayout width={600} height={500} legend={legend}>
|
||||
{(vizWidth: number, vizHeight: number) => {
|
||||
return <div style={{ width: vizWidth, height: vizHeight, background: 'red' }} />;
|
||||
}}
|
||||
@ -57,7 +57,7 @@ export const RightLegend = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<VizLayout width={400} height={500} legend={legend}>
|
||||
<VizLayout width={810} height={400} legend={legend}>
|
||||
{(vizWidth: number, vizHeight: number) => {
|
||||
return <div style={{ width: vizWidth, height: vizHeight, background: 'red' }} />;
|
||||
}}
|
||||
|
@ -38,7 +38,7 @@ export const VizLayout: VizLayoutComponentType = ({ width, height, legend, child
|
||||
let size: VizSize | null = null;
|
||||
|
||||
const vizStyle: CSSProperties = {
|
||||
flexGrow: 1,
|
||||
flexGrow: 2,
|
||||
};
|
||||
|
||||
const legendStyle: CSSProperties = {};
|
||||
@ -62,6 +62,16 @@ export const VizLayout: VizLayoutComponentType = ({ width, height, legend, child
|
||||
break;
|
||||
}
|
||||
|
||||
// This happens when position is switched from bottom to right
|
||||
// Then we preserve old with for one render cycle until lenged is measured in it's new position
|
||||
if (size?.width === 0) {
|
||||
size.width = width;
|
||||
}
|
||||
|
||||
if (size?.height === 0) {
|
||||
size.height = height;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={containerStyle}>
|
||||
<div style={vizStyle}>{size && children(size.width, size.height)}</div>
|
||||
|
@ -119,7 +119,7 @@ function calculateAxisSize(self: uPlot, values: string[], axisIdx: number) {
|
||||
}
|
||||
}
|
||||
|
||||
return measureText(maxLength, 12).width;
|
||||
return measureText(maxLength, 12).width - 8;
|
||||
}
|
||||
|
||||
/** Format time axis ticks */
|
||||
|
@ -17,7 +17,7 @@ describe('usePlotConfig', () => {
|
||||
"alpha": 1,
|
||||
},
|
||||
"gutters": Object {
|
||||
"x": 16,
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
},
|
||||
"height": 0,
|
||||
@ -59,7 +59,7 @@ describe('usePlotConfig', () => {
|
||||
"alpha": 1,
|
||||
},
|
||||
"gutters": Object {
|
||||
"x": 16,
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
},
|
||||
"height": 0,
|
||||
@ -108,7 +108,7 @@ describe('usePlotConfig', () => {
|
||||
"alpha": 1,
|
||||
},
|
||||
"gutters": Object {
|
||||
"x": 16,
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
},
|
||||
"height": 0,
|
||||
@ -155,7 +155,7 @@ describe('usePlotConfig', () => {
|
||||
"alpha": 1,
|
||||
},
|
||||
"gutters": Object {
|
||||
"x": 16,
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
},
|
||||
"height": 0,
|
||||
@ -203,7 +203,7 @@ describe('usePlotConfig', () => {
|
||||
"alpha": 1,
|
||||
},
|
||||
"gutters": Object {
|
||||
"x": 16,
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
},
|
||||
"height": 0,
|
||||
@ -253,7 +253,7 @@ describe('usePlotConfig', () => {
|
||||
"alpha": 1,
|
||||
},
|
||||
"gutters": Object {
|
||||
"x": 16,
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
},
|
||||
"height": 0,
|
||||
@ -297,7 +297,7 @@ describe('usePlotConfig', () => {
|
||||
"alpha": 1,
|
||||
},
|
||||
"gutters": Object {
|
||||
"x": 16,
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
},
|
||||
"height": 0,
|
||||
@ -341,7 +341,7 @@ describe('usePlotConfig', () => {
|
||||
"alpha": 1,
|
||||
},
|
||||
"gutters": Object {
|
||||
"x": 16,
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
},
|
||||
"height": 0,
|
||||
@ -391,7 +391,7 @@ describe('usePlotConfig', () => {
|
||||
"alpha": 1,
|
||||
},
|
||||
"gutters": Object {
|
||||
"x": 16,
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
},
|
||||
"height": 0,
|
||||
@ -439,7 +439,7 @@ describe('usePlotConfig', () => {
|
||||
"alpha": 1,
|
||||
},
|
||||
"gutters": Object {
|
||||
"x": 16,
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
},
|
||||
"height": 0,
|
||||
@ -484,7 +484,7 @@ describe('usePlotConfig', () => {
|
||||
"alpha": 1,
|
||||
},
|
||||
"gutters": Object {
|
||||
"x": 16,
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
},
|
||||
"height": 0,
|
||||
@ -538,7 +538,7 @@ describe('usePlotConfig', () => {
|
||||
"alpha": 1,
|
||||
},
|
||||
"gutters": Object {
|
||||
"x": 16,
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
},
|
||||
"height": 0,
|
||||
|
@ -95,7 +95,7 @@ export const DEFAULT_PLOT_CONFIG = {
|
||||
show: false,
|
||||
},
|
||||
gutters: {
|
||||
x: 16,
|
||||
x: 8,
|
||||
y: 8,
|
||||
},
|
||||
series: [],
|
||||
|
@ -113,9 +113,8 @@ class UnThemedExploreGraphPanel extends PureComponent<Props, State> {
|
||||
return (
|
||||
<GraphWithLegend
|
||||
ariaLabel={ariaLabel}
|
||||
displayMode={LegendDisplayMode.List}
|
||||
legendDisplayMode={LegendDisplayMode.List}
|
||||
height={height}
|
||||
isLegendVisible={true}
|
||||
placement={'bottom'}
|
||||
width={width}
|
||||
timeRange={timeRange}
|
||||
|
@ -92,7 +92,7 @@ export const decorateWithGraphResult = (data: ExplorePanelData): ExplorePanelDat
|
||||
data.request?.timezone ?? 'browser',
|
||||
{},
|
||||
{ showBars: false, showLines: true, showPoints: false },
|
||||
{ displayMode: LegendDisplayMode.List, isVisible: true, placement: 'bottom' }
|
||||
{ displayMode: LegendDisplayMode.List, placement: 'bottom' }
|
||||
);
|
||||
|
||||
return { ...data, graphResult };
|
||||
|
@ -53,8 +53,7 @@ export const GraphPanel: React.FunctionComponent<GraphPanelProps> = ({
|
||||
timeZone={timeZone}
|
||||
width={width}
|
||||
height={height}
|
||||
displayMode={legendOptions.displayMode}
|
||||
isLegendVisible={legendOptions.isVisible}
|
||||
legendDisplayMode={legendOptions.displayMode}
|
||||
placement={legendOptions.placement}
|
||||
sortLegendBy={legendOptions.sortBy}
|
||||
sortLegendDesc={legendOptions.sortDesc}
|
||||
|
@ -28,7 +28,6 @@ export const defaults: Options = {
|
||||
showPoints: false,
|
||||
},
|
||||
legend: {
|
||||
isVisible: true,
|
||||
displayMode: LegendDisplayMode.List,
|
||||
placement: 'bottom',
|
||||
},
|
||||
|
@ -22,7 +22,7 @@ export const GraphPanel: React.FC<GraphPanelProps> = ({
|
||||
timeRange={timeRange}
|
||||
timeZone={timeZone}
|
||||
width={width}
|
||||
height={height - 8}
|
||||
height={height}
|
||||
legend={options.legend}
|
||||
>
|
||||
<TooltipPlugin mode={options.tooltipOptions.mode as any} timeZone={timeZone} />
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
||||
import { AxisSide, GraphCustomFieldConfig } from '@grafana/ui';
|
||||
import { AxisSide, GraphCustomFieldConfig, LegendDisplayMode } from '@grafana/ui';
|
||||
import { GraphPanel } from './GraphPanel';
|
||||
import { Options } from './types';
|
||||
|
||||
export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPanel)
|
||||
.setNoPadding()
|
||||
.useFieldConfig({
|
||||
standardOptions: {
|
||||
[FieldConfigProperty.Color]: {
|
||||
@ -133,29 +132,26 @@ export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPane
|
||||
defaultValue: 'single',
|
||||
settings: {
|
||||
options: [
|
||||
{ value: 'single', label: 'Single series' },
|
||||
{ value: 'multi', label: 'All series' },
|
||||
{ value: 'none', label: 'No tooltip' },
|
||||
{ value: 'single', label: 'Single' },
|
||||
{ value: 'multi', label: 'All' },
|
||||
{ value: 'none', label: 'Hidden' },
|
||||
],
|
||||
},
|
||||
})
|
||||
.addBooleanSwitch({
|
||||
category: ['Legend'],
|
||||
path: 'legend.isVisible',
|
||||
name: 'Show legend',
|
||||
.addRadio({
|
||||
path: 'legend.displayMode',
|
||||
name: 'Legend mode',
|
||||
description: '',
|
||||
defaultValue: true,
|
||||
})
|
||||
.addBooleanSwitch({
|
||||
category: ['Legend'],
|
||||
path: 'legend.asTable',
|
||||
name: 'Display legend as table',
|
||||
description: '',
|
||||
defaultValue: false,
|
||||
showIf: c => c.legend.isVisible,
|
||||
defaultValue: LegendDisplayMode.List,
|
||||
settings: {
|
||||
options: [
|
||||
{ value: LegendDisplayMode.List, label: 'List' },
|
||||
{ value: LegendDisplayMode.Table, label: 'Table' },
|
||||
{ value: LegendDisplayMode.Hidden, label: 'Hidden' },
|
||||
],
|
||||
},
|
||||
})
|
||||
.addRadio({
|
||||
category: ['Legend'],
|
||||
path: 'legend.placement',
|
||||
name: 'Legend placement',
|
||||
description: '',
|
||||
@ -166,6 +162,6 @@ export const plugin = new PanelPlugin<Options, GraphCustomFieldConfig>(GraphPane
|
||||
{ value: 'right', label: 'Right' },
|
||||
],
|
||||
},
|
||||
showIf: c => c.legend.isVisible,
|
||||
showIf: c => c.legend.displayMode !== LegendDisplayMode.Hidden,
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user