Commit 28ce2f12 by Torkel Ödegaard Committed by GitHub

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
parent ec864f64
......@@ -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} />
......
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',
description: '',
defaultValue: true,
})
.addBooleanSwitch({
category: ['Legend'],
path: 'legend.asTable',
name: 'Display legend as table',
.addRadio({
path: 'legend.displayMode',
name: 'Legend mode',
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,
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment