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:
Torkel Ödegaard
2020-11-13 17:08:55 +01:00
committed by GitHub
parent ec864f6461
commit 28ce2f12ed
19 changed files with 83 additions and 69 deletions

View File

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

View File

@ -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),

View File

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

View File

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

View File

@ -35,7 +35,6 @@ const mockData = () => {
};
const defaultLegendOptions: LegendOptions = {
isVisible: false,
displayMode: LegendDisplayMode.List,
placement: 'bottom',
};

View File

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

View File

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

View File

@ -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[];
}

View File

@ -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' }} />;
}}

View File

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

View File

@ -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 */

View File

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

View File

@ -95,7 +95,7 @@ export const DEFAULT_PLOT_CONFIG = {
show: false,
},
gutters: {
x: 16,
x: 8,
y: 8,
},
series: [],

View File

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

View File

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

View File

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

View File

@ -28,7 +28,6 @@ export const defaults: Options = {
showPoints: false,
},
legend: {
isVisible: true,
displayMode: LegendDisplayMode.List,
placement: 'bottom',
},

View File

@ -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} />

View File

@ -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,
});
});