BarChart: enable series toggling via legend (#33955)

* Adds support for Tooltip in BarChartPanel

* Revert some formatting

* Remove BarChart story

* BarChart: move visualization to core

* BarChart: enable series toggling via legend

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
Dominik Prokop 2021-05-11 21:40:04 +02:00 committed by GitHub
parent 24b878ca4f
commit 2ded2aef71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 53 additions and 135 deletions

View File

@ -41,7 +41,7 @@ export interface GraphNGProps extends Themeable2 {
prepConfig: (alignedFrame: DataFrame, getTimeRange: () => TimeRange) => UPlotConfigBuilder;
propsToDiff?: string[];
preparePlotFrame?: (frames: DataFrame[], dimFields: XYFieldMatchers) => DataFrame;
renderLegend: (config: UPlotConfigBuilder) => React.ReactElement;
renderLegend: (config: UPlotConfigBuilder) => React.ReactElement | null;
}
function sameProps(prevProps: any, nextProps: any, propsToDiff: string[] = []) {

View File

@ -23,22 +23,13 @@ export class UnthemedTimeSeries extends React.Component<TimeSeriesProps> {
};
renderLegend = (config: UPlotConfigBuilder) => {
const { legend, onLegendClick, frames } = this.props;
const { legend, frames } = this.props;
if (!config || (legend && legend.displayMode === LegendDisplayMode.Hidden)) {
return;
return null;
}
return (
<PlotLegend
data={frames}
config={config}
onLegendClick={onLegendClick}
maxHeight="35%"
maxWidth="60%"
{...legend}
/>
);
return <PlotLegend data={frames} config={config} maxHeight="35%" maxWidth="60%" {...legend} />;
};
render() {

View File

@ -9,7 +9,7 @@ import { LegendPlacement } from '..';
export interface VizLayoutProps {
width: number;
height: number;
legend?: React.ReactElement<VizLayoutLegendProps>;
legend?: React.ReactElement<VizLayoutLegendProps> | null;
children: (width: number, height: number) => React.ReactNode;
}

View File

@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React from 'react';
import { DataFrame, DisplayValue, fieldReducers, getFieldDisplayName, reduceField } from '@grafana/data';
import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
import { VizLegendItem } from '../VizLegend/types';
@ -6,42 +6,22 @@ import { VizLegendOptions } from '../VizLegend/models.gen';
import { AxisPlacement } from './config';
import { VizLayout, VizLayoutLegendProps } from '../VizLayout/VizLayout';
import { VizLegend } from '../VizLegend/VizLegend';
import { GraphNGLegendEvent } from '..';
import { mapMouseEventToMode } from '../VizLegend/utils';
const defaultFormatter = (v: any) => (v == null ? '-' : v.toFixed(1));
interface PlotLegendProps extends VizLegendOptions, Omit<VizLayoutLegendProps, 'children'> {
data: DataFrame[];
config: UPlotConfigBuilder;
onLegendClick?: (event: GraphNGLegendEvent) => void;
}
export const PlotLegend: React.FC<PlotLegendProps> = ({
data,
config,
onLegendClick,
placement,
calcs,
displayMode,
...vizLayoutLegendProps
}) => {
const onLegendLabelClick = useCallback(
(legend: VizLegendItem, event: React.MouseEvent) => {
const { fieldIndex } = legend;
if (!onLegendClick || !fieldIndex) {
return;
}
onLegendClick({
fieldIndex,
mode: mapMouseEventToMode(event),
});
},
[onLegendClick]
);
const legendItems = config
.getSeries()
.map<VizLegendItem | undefined>((s) => {
@ -92,12 +72,7 @@ export const PlotLegend: React.FC<PlotLegendProps> = ({
return (
<VizLayout.Legend placement={placement} {...vizLayoutLegendProps}>
<VizLegend
onLabelClick={onLegendLabelClick}
placement={placement}
items={legendItems}
displayMode={displayMode}
/>
<VizLegend placement={placement} items={legendItems} displayMode={displayMode} />
</VizLayout.Legend>
);
};

View File

@ -1,6 +1,14 @@
import React from 'react';
import { DataFrame, TimeRange } from '@grafana/data';
import { GraphNG, GraphNGProps, LegendDisplayMode, PlotLegend, UPlotConfigBuilder, withTheme2 } from '@grafana/ui';
import {
GraphNG,
GraphNGProps,
LegendDisplayMode,
PlotLegend,
UPlotConfigBuilder,
usePanelContext,
useTheme2,
} from '@grafana/ui';
import { BarChartOptions } from './types';
import { preparePlotConfigBuilder, preparePlotFrame } from './utils';
@ -9,14 +17,24 @@ import { preparePlotConfigBuilder, preparePlotFrame } from './utils';
*/
export interface BarChartProps
extends BarChartOptions,
Omit<GraphNGProps, 'prepConfig' | 'propsToDiff' | 'renderLegend'> {}
Omit<GraphNGProps, 'prepConfig' | 'propsToDiff' | 'renderLegend' | 'theme'> {}
const propsToDiff: string[] = ['orientation', 'barWidth', 'groupWidth', 'showValue'];
class UnthemedBarChart extends React.Component<BarChartProps> {
prepConfig = (alignedFrame: DataFrame, getTimeRange: () => TimeRange) => {
const { eventBus } = this.context;
const { theme, timeZone, orientation, barWidth, showValue, groupWidth, stacking, legend, tooltip } = this.props;
export const BarChart: React.FC<BarChartProps> = (props) => {
const theme = useTheme2();
const { eventBus } = usePanelContext();
const renderLegend = (config: UPlotConfigBuilder) => {
if (!config || props.legend.displayMode === LegendDisplayMode.Hidden) {
return null;
}
return <PlotLegend data={props.frames} config={config} maxHeight="35%" maxWidth="60%" {...props.legend} />;
};
const prepConfig = (alignedFrame: DataFrame, getTimeRange: () => TimeRange) => {
const { timeZone, orientation, barWidth, showValue, groupWidth, stacking, legend, tooltip } = props;
return preparePlotConfigBuilder({
frame: alignedFrame,
getTimeRange,
@ -33,38 +51,16 @@ class UnthemedBarChart extends React.Component<BarChartProps> {
});
};
renderLegend = (config: UPlotConfigBuilder) => {
const { legend, onLegendClick, frames } = this.props;
if (!config || legend.displayMode === LegendDisplayMode.Hidden) {
return;
}
return (
<PlotLegend
data={frames}
config={config}
onLegendClick={onLegendClick}
maxHeight="35%"
maxWidth="60%"
{...legend}
/>
);
};
render() {
return (
<GraphNG
{...this.props}
frames={this.props.frames}
prepConfig={this.prepConfig}
propsToDiff={propsToDiff}
preparePlotFrame={preparePlotFrame}
renderLegend={this.renderLegend as any}
/>
);
}
}
export const BarChart = withTheme2(UnthemedBarChart);
return (
<GraphNG
{...props}
theme={theme}
frames={props.frames}
prepConfig={prepConfig}
propsToDiff={propsToDiff}
preparePlotFrame={preparePlotFrame}
renderLegend={renderLegend}
/>
);
};
BarChart.displayName = 'BarChart';

View File

@ -1,7 +1,6 @@
import React, { useCallback, useMemo } from 'react';
import React, { useMemo } from 'react';
import { FieldType, PanelProps, TimeRange, VizOrientation } from '@grafana/data';
import { GraphNGLegendEvent, TooltipPlugin } from '@grafana/ui';
import { hideSeriesConfigFactory } from '../timeseries/overrides/hideSeriesConfigFactory';
import { TooltipPlugin } from '@grafana/ui';
import { BarChartOptions } from './types';
import { BarChart } from './BarChart';
@ -10,15 +9,7 @@ interface Props extends PanelProps<BarChartOptions> {}
/**
* @alpha
*/
export const BarChartPanel: React.FunctionComponent<Props> = ({
data,
options,
width,
height,
fieldConfig,
timeZone,
onFieldConfigChange,
}) => {
export const BarChartPanel: React.FunctionComponent<Props> = ({ data, options, width, height, timeZone }) => {
const orientation = useMemo(() => {
if (!options.orientation || options.orientation === VizOrientation.Auto) {
return width < height ? VizOrientation.Horizontal : VizOrientation.Vertical;
@ -27,13 +18,6 @@ export const BarChartPanel: React.FunctionComponent<Props> = ({
return options.orientation;
}, [width, height, options.orientation]);
const onLegendClick = useCallback(
(event: GraphNGLegendEvent) => {
onFieldConfigChange(hideSeriesConfigFactory(event, fieldConfig, data.series));
},
[fieldConfig, onFieldConfigChange, data.series]
);
if (!data || !data.series?.length) {
return (
<div className="panel-empty">
@ -66,7 +50,6 @@ export const BarChartPanel: React.FunctionComponent<Props> = ({
structureRev={data.structureRev}
width={width}
height={height}
onLegendClick={onLegendClick}
{...options}
orientation={orientation}
>

View File

@ -18,8 +18,8 @@ import {
ScaleDistribution,
ScaleOrientation,
UPlotConfigBuilder,
UPlotConfigPrepFn,
} from '@grafana/ui';
import { UPlotConfigPrepFn } from '@grafana/ui/src/components/uPlot/config/UPlotConfigBuilder';
/** @alpha */
function getBarCharScaleOrientation(orientation: VizOrientation) {

View File

@ -1,12 +1,5 @@
import React from 'react';
import {
PanelContext,
PanelContextRoot,
UPlotConfigBuilder,
GraphNG,
GraphNGProps,
BarValueVisibility,
} from '@grafana/ui';
import { PanelContext, PanelContextRoot, GraphNG, GraphNGProps, BarValueVisibility } from '@grafana/ui';
import { DataFrame, FieldType, TimeRange } from '@grafana/data';
import { preparePlotConfigBuilder } from './utils';
import { TimelineMode } from './types';
@ -39,9 +32,7 @@ export class TimelineChart extends React.Component<TimelineProps> {
});
};
renderLegend = (config: UPlotConfigBuilder) => {
return;
};
renderLegend = () => null;
render() {
return (
@ -53,7 +44,7 @@ export class TimelineChart extends React.Component<TimelineProps> {
}}
prepConfig={this.prepConfig}
propsToDiff={propsToDiff}
renderLegend={this.renderLegend as any}
renderLegend={this.renderLegend}
/>
);
}

View File

@ -1,7 +1,6 @@
import React, { useCallback } from 'react';
import React from 'react';
import { PanelProps } from '@grafana/data';
import { GraphNGLegendEvent, useTheme2 } from '@grafana/ui';
import { hideSeriesConfigFactory } from '../timeseries/overrides/hideSeriesConfigFactory';
import { useTheme2 } from '@grafana/ui';
import { TimelineOptions } from './types';
import { TimelineChart } from './TimelineChart';
@ -10,25 +9,9 @@ interface TimelinePanelProps extends PanelProps<TimelineOptions> {}
/**
* @alpha
*/
export const TimelinePanel: React.FC<TimelinePanelProps> = ({
data,
timeRange,
timeZone,
options,
width,
height,
fieldConfig,
onFieldConfigChange,
}) => {
export const TimelinePanel: React.FC<TimelinePanelProps> = ({ data, timeRange, timeZone, options, width, height }) => {
const theme = useTheme2();
const onLegendClick = useCallback(
(event: GraphNGLegendEvent) => {
onFieldConfigChange(hideSeriesConfigFactory(event, fieldConfig, data.series));
},
[fieldConfig, onFieldConfigChange, data.series]
);
if (!data || !data.series?.length) {
return (
<div className="panel-empty">
@ -46,7 +29,6 @@ export const TimelinePanel: React.FC<TimelinePanelProps> = ({
timeZone={timeZone}
width={width}
height={height}
onLegendClick={onLegendClick}
{...options}
/>
);