mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PieChart: move the pie chart component into the panel (#33800)
* move pie chart * move pie chart * Pass displayLabels to piechart Co-authored-by: Oscar Kilhed <oscar.kilhed@grafana.com>
This commit is contained in:
@@ -1,317 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Meta, Story } from '@storybook/react';
|
||||
import { PieChart, PieChartProps, PieChartType, TooltipDisplayMode } from '@grafana/ui';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import {
|
||||
FieldColorModeId,
|
||||
FieldConfigSource,
|
||||
FieldType,
|
||||
InterpolateFunction,
|
||||
ReduceDataOptions,
|
||||
ThresholdsMode,
|
||||
toDataFrame,
|
||||
} from '@grafana/data';
|
||||
import { LegendDisplayMode, LegendPlacement } from '../VizLegend/models.gen';
|
||||
import { PieChartLabels, PieChartLegendValues } from './types';
|
||||
|
||||
const fieldConfig: FieldConfigSource = {
|
||||
defaults: {
|
||||
thresholds: {
|
||||
mode: ThresholdsMode.Percentage,
|
||||
steps: [{ color: 'green', value: 0 }],
|
||||
},
|
||||
color: {
|
||||
mode: FieldColorModeId.PaletteClassic,
|
||||
},
|
||||
},
|
||||
overrides: [],
|
||||
};
|
||||
const reduceOptions: ReduceDataOptions = { calcs: [] };
|
||||
const replaceVariables: InterpolateFunction = (v) => v;
|
||||
const data = [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
type: FieldType.time,
|
||||
config: {
|
||||
color: {
|
||||
mode: 'palette-classic',
|
||||
},
|
||||
},
|
||||
values: [1620110299324, 1620110329324],
|
||||
state: {
|
||||
displayName: null,
|
||||
seriesIndex: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Cellar',
|
||||
type: FieldType.number,
|
||||
config: {
|
||||
color: {
|
||||
mode: 'palette-classic',
|
||||
},
|
||||
},
|
||||
values: [27.008127592438484, 27.287423219155304],
|
||||
state: {
|
||||
displayName: 'Cellar',
|
||||
seriesIndex: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
type: FieldType.time,
|
||||
config: {
|
||||
color: {
|
||||
mode: 'palette-classic',
|
||||
},
|
||||
},
|
||||
values: [1620110299324, 1620110329324],
|
||||
state: {
|
||||
displayName: null,
|
||||
seriesIndex: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Living room',
|
||||
type: FieldType.number,
|
||||
config: {
|
||||
color: {
|
||||
mode: 'palette-classic',
|
||||
},
|
||||
},
|
||||
values: [65.97347908625065, 65.6588311671438],
|
||||
state: {
|
||||
displayName: 'Living room',
|
||||
seriesIndex: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
type: FieldType.time,
|
||||
config: {
|
||||
color: {
|
||||
mode: 'palette-classic',
|
||||
},
|
||||
},
|
||||
values: [1620110299324, 1620110329324],
|
||||
state: {
|
||||
displayName: null,
|
||||
seriesIndex: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Porch',
|
||||
type: FieldType.number,
|
||||
config: {
|
||||
color: {
|
||||
mode: 'palette-classic',
|
||||
},
|
||||
},
|
||||
values: [42.01219662436388, 42.48471312850685],
|
||||
state: {
|
||||
displayName: 'Porch',
|
||||
seriesIndex: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
type: FieldType.time,
|
||||
config: {
|
||||
color: {
|
||||
mode: 'palette-classic',
|
||||
},
|
||||
},
|
||||
values: [1620110299324, 1620110329324],
|
||||
state: {
|
||||
displayName: null,
|
||||
seriesIndex: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Bedroom',
|
||||
type: FieldType.number,
|
||||
config: {
|
||||
color: {
|
||||
mode: 'palette-classic',
|
||||
},
|
||||
},
|
||||
values: [34.28143812581964, 34.37741979130198],
|
||||
state: {
|
||||
displayName: 'Bedroom',
|
||||
seriesIndex: 4,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
type: FieldType.time,
|
||||
config: {
|
||||
color: {
|
||||
mode: 'palette-classic',
|
||||
},
|
||||
mappings: [],
|
||||
},
|
||||
values: [1620110299324, 1620110329324],
|
||||
state: {
|
||||
displayName: null,
|
||||
seriesIndex: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Guest room',
|
||||
type: FieldType.number,
|
||||
config: {
|
||||
color: {
|
||||
mode: 'palette-classic',
|
||||
},
|
||||
},
|
||||
values: [57.855438763786104, 57.521663794462654],
|
||||
state: {
|
||||
displayName: 'Guest room',
|
||||
seriesIndex: 5,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
title: 'Visualizations/PieChart',
|
||||
decorators: [withCenteredStory],
|
||||
component: PieChart,
|
||||
args: {
|
||||
width: 500,
|
||||
height: 500,
|
||||
fieldConfig,
|
||||
data,
|
||||
},
|
||||
argTypes: {
|
||||
tooltipMode: {
|
||||
control: {
|
||||
type: 'select',
|
||||
options: Object.values(TooltipDisplayMode),
|
||||
},
|
||||
},
|
||||
legendDisplayMode: {
|
||||
control: {
|
||||
type: 'select',
|
||||
options: Object.values(LegendDisplayMode),
|
||||
},
|
||||
},
|
||||
legendPlacement: {
|
||||
control: {
|
||||
type: 'select',
|
||||
options: ['bottom', 'right'],
|
||||
},
|
||||
},
|
||||
legendValues: {
|
||||
control: {
|
||||
type: 'check',
|
||||
options: Object.values(PieChartLegendValues),
|
||||
},
|
||||
},
|
||||
displayLabels: {
|
||||
control: {
|
||||
type: 'check',
|
||||
options: Object.values(PieChartLabels),
|
||||
},
|
||||
},
|
||||
height: {
|
||||
control: { type: 'range', min: 300, max: 1000, step: 50 },
|
||||
},
|
||||
width: {
|
||||
control: { type: 'range', min: 300, max: 1000, step: 50 },
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
controls: {
|
||||
exclude: [
|
||||
'reduceOptions',
|
||||
'replaceVariables',
|
||||
'tooltipOptions',
|
||||
'onSeriesColorChange',
|
||||
'legendOptions',
|
||||
'timeZone',
|
||||
],
|
||||
},
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
interface PieChartStoryProps extends PieChartProps {
|
||||
tooltipMode: TooltipDisplayMode;
|
||||
legendDisplayMode: LegendDisplayMode;
|
||||
legendPlacement: LegendPlacement;
|
||||
legendValues: PieChartLegendValues[];
|
||||
}
|
||||
|
||||
const Template: Story<PieChartStoryProps> = ({
|
||||
tooltipMode,
|
||||
legendDisplayMode,
|
||||
legendPlacement,
|
||||
legendValues,
|
||||
data,
|
||||
...args
|
||||
}) => {
|
||||
const tooltipOpts = {
|
||||
mode: tooltipMode,
|
||||
};
|
||||
|
||||
const legendOptions = {
|
||||
displayMode: legendDisplayMode,
|
||||
placement: legendPlacement,
|
||||
values: legendValues,
|
||||
calcs: [],
|
||||
};
|
||||
|
||||
const dataPoints = data.map((d) => toDataFrame(d));
|
||||
|
||||
return (
|
||||
<PieChart
|
||||
{...args}
|
||||
data={dataPoints}
|
||||
tooltipOptions={tooltipOpts}
|
||||
legendOptions={legendOptions}
|
||||
replaceVariables={replaceVariables}
|
||||
reduceOptions={reduceOptions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const basic = Template.bind({});
|
||||
|
||||
basic.args = {
|
||||
pieType: PieChartType.Pie,
|
||||
tooltipMode: TooltipDisplayMode.Single,
|
||||
legendDisplayMode: LegendDisplayMode.List,
|
||||
legendPlacement: 'bottom',
|
||||
legendValues: [PieChartLegendValues.Value],
|
||||
displayLabels: [PieChartLabels.Name],
|
||||
};
|
||||
|
||||
export const donut = Template.bind({});
|
||||
|
||||
donut.args = {
|
||||
pieType: PieChartType.Donut,
|
||||
tooltipMode: TooltipDisplayMode.Single,
|
||||
legendDisplayMode: LegendDisplayMode.List,
|
||||
legendPlacement: 'bottom',
|
||||
legendValues: [PieChartLegendValues.Value],
|
||||
displayLabels: [PieChartLabels.Name],
|
||||
};
|
@@ -1,67 +0,0 @@
|
||||
import { DataFrame, FieldConfigSource, FieldDisplay, InterpolateFunction, ReduceDataOptions } from '@grafana/data';
|
||||
import { VizTooltipOptions } from '../VizTooltip';
|
||||
import { VizLegendOptions } from '..';
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export interface PieChartSvgProps {
|
||||
height: number;
|
||||
width: number;
|
||||
fieldDisplayValues: FieldDisplay[];
|
||||
pieType: PieChartType;
|
||||
highlightedTitle?: string;
|
||||
displayLabels?: PieChartLabels[];
|
||||
useGradients?: boolean;
|
||||
tooltipOptions: VizTooltipOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export interface PieChartProps {
|
||||
height: number;
|
||||
width: number;
|
||||
pieType: PieChartType;
|
||||
displayLabels?: PieChartLabels[];
|
||||
useGradients?: boolean;
|
||||
legendOptions?: PieChartLegendOptions;
|
||||
tooltipOptions: VizTooltipOptions;
|
||||
reduceOptions: ReduceDataOptions;
|
||||
fieldConfig: FieldConfigSource<any>;
|
||||
replaceVariables: InterpolateFunction;
|
||||
data: DataFrame[];
|
||||
timeZone?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export enum PieChartType {
|
||||
Pie = 'pie',
|
||||
Donut = 'donut',
|
||||
}
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export enum PieChartLegendValues {
|
||||
Value = 'value',
|
||||
Percent = 'percent',
|
||||
}
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export enum PieChartLabels {
|
||||
Name = 'name',
|
||||
Value = 'value',
|
||||
Percent = 'percent',
|
||||
}
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export interface PieChartLegendOptions extends VizLegendOptions {
|
||||
values: PieChartLegendValues[];
|
||||
}
|
@@ -18,14 +18,6 @@ export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker';
|
||||
export { ColorValueEditor, ColorValueEditorProps } from './OptionsUI/color';
|
||||
export { SeriesColorPickerPopover, SeriesColorPickerPopoverWithTheme } from './ColorPicker/SeriesColorPickerPopover';
|
||||
export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult';
|
||||
export { PieChart } from './PieChart/PieChart';
|
||||
export {
|
||||
PieChartType,
|
||||
PieChartProps,
|
||||
PieChartLabels,
|
||||
PieChartLegendOptions,
|
||||
PieChartLegendValues,
|
||||
} from './PieChart/types';
|
||||
export { UnitPicker } from './UnitPicker/UnitPicker';
|
||||
export { StatsPicker } from './StatsPicker/StatsPicker';
|
||||
export { RefreshPicker, defaultIntervals } from './RefreshPicker/RefreshPicker';
|
||||
@@ -105,7 +97,7 @@ export {
|
||||
usePanelContext,
|
||||
} from './PanelChrome';
|
||||
export { VizLayout, VizLayoutComponentType, VizLayoutLegendProps, VizLayoutProps } from './VizLayout/VizLayout';
|
||||
export { VizLegendItem } from './VizLegend/types';
|
||||
export { VizLegendItem, SeriesVisibilityChangeBehavior } from './VizLegend/types';
|
||||
export { LegendPlacement, LegendDisplayMode, VizLegendOptions } from './VizLegend/models.gen';
|
||||
export { VizLegend } from './VizLegend/VizLegend';
|
||||
|
||||
|
@@ -1,176 +1,43 @@
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import { FieldDisplay, FALLBACK_COLOR, formattedValueToString, GrafanaTheme2 } from '@grafana/data';
|
||||
import {
|
||||
DataHoverClearEvent,
|
||||
DataHoverEvent,
|
||||
FALLBACK_COLOR,
|
||||
FieldDisplay,
|
||||
formattedValueToString,
|
||||
getFieldDisplayValues,
|
||||
GrafanaTheme2,
|
||||
} from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { useStyles2, useTheme2 } from '../../themes/ThemeContext';
|
||||
import tinycolor from 'tinycolor2';
|
||||
VizTooltipOptions,
|
||||
useTheme2,
|
||||
useStyles2,
|
||||
SeriesTableRowProps,
|
||||
DataLinksContextMenu,
|
||||
SeriesTable,
|
||||
} from '@grafana/ui';
|
||||
import { PieChartType, PieChartLabels } from './types';
|
||||
import { useTooltip, useTooltipInPortal } from '@visx/tooltip';
|
||||
import Pie, { PieArcDatum, ProvidedProps } from '@visx/shape/lib/shapes/Pie';
|
||||
import { Group } from '@visx/group';
|
||||
import { UseTooltipParams } from '@visx/tooltip/lib/hooks/useTooltip';
|
||||
import { RadialGradient } from '@visx/gradient';
|
||||
import { localPoint } from '@visx/event';
|
||||
import { useTooltip, useTooltipInPortal } from '@visx/tooltip';
|
||||
import { useComponentInstanceId } from '../../utils/useComponetInstanceId';
|
||||
import { Group } from '@visx/group';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { css } from '@emotion/css';
|
||||
import { VizLegend, VizLegendItem } from '..';
|
||||
import { VizLayout } from '../VizLayout/VizLayout';
|
||||
import { LegendDisplayMode } from '../VizLegend/models.gen';
|
||||
import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu';
|
||||
import { UseTooltipParams } from '@visx/tooltip/lib/hooks/useTooltip';
|
||||
import {
|
||||
PieChartLabels,
|
||||
PieChartLegendOptions,
|
||||
PieChartLegendValues,
|
||||
PieChartProps,
|
||||
PieChartSvgProps,
|
||||
PieChartType,
|
||||
} from './types';
|
||||
import { getTooltipContainerStyles } from '../../themes/mixins';
|
||||
import { SeriesTable, SeriesTableRowProps, VizTooltipOptions } from '../VizTooltip';
|
||||
import { usePanelContext } from '../PanelChrome';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { SeriesVisibilityChangeBehavior } from '../VizLegend/types';
|
||||
|
||||
const defaultLegendOptions: PieChartLegendOptions = {
|
||||
displayMode: LegendDisplayMode.List,
|
||||
placement: 'right',
|
||||
calcs: [],
|
||||
values: [PieChartLegendValues.Percent],
|
||||
};
|
||||
import { useComponentInstanceId } from '@grafana/ui/src/utils/useComponetInstanceId';
|
||||
import { getTooltipContainerStyles } from '@grafana/ui/src/themes/mixins';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export function PieChart(props: PieChartProps) {
|
||||
const {
|
||||
data,
|
||||
timeZone,
|
||||
reduceOptions,
|
||||
fieldConfig,
|
||||
replaceVariables,
|
||||
tooltipOptions,
|
||||
width,
|
||||
height,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const theme = useTheme2();
|
||||
const highlightedTitle = useSliceHighlightState();
|
||||
const fieldDisplayValues = getFieldDisplayValues({
|
||||
fieldConfig,
|
||||
reduceOptions,
|
||||
data,
|
||||
theme: theme,
|
||||
replaceVariables,
|
||||
timeZone,
|
||||
});
|
||||
|
||||
return (
|
||||
<VizLayout width={width} height={height} legend={getLegend(props, fieldDisplayValues)}>
|
||||
{(vizWidth: number, vizHeight: number) => {
|
||||
return (
|
||||
<PieChartSvg
|
||||
width={vizWidth}
|
||||
height={vizHeight}
|
||||
highlightedTitle={highlightedTitle}
|
||||
fieldDisplayValues={fieldDisplayValues}
|
||||
tooltipOptions={tooltipOptions}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</VizLayout>
|
||||
);
|
||||
interface PieChartProps {
|
||||
height: number;
|
||||
width: number;
|
||||
fieldDisplayValues: FieldDisplay[];
|
||||
pieType: PieChartType;
|
||||
highlightedTitle?: string;
|
||||
displayLabels?: PieChartLabels[];
|
||||
useGradients?: boolean; // not used?
|
||||
tooltipOptions: VizTooltipOptions;
|
||||
}
|
||||
|
||||
function getLegend(props: PieChartProps, displayValues: FieldDisplay[]) {
|
||||
const { legendOptions = defaultLegendOptions } = props;
|
||||
|
||||
if (legendOptions.displayMode === LegendDisplayMode.Hidden) {
|
||||
return undefined;
|
||||
}
|
||||
const total = displayValues
|
||||
.filter((item) => {
|
||||
return !item.field.custom.hideFrom.viz;
|
||||
})
|
||||
.reduce((acc, item) => item.display.numeric + acc, 0);
|
||||
|
||||
const legendItems = displayValues.map<VizLegendItem>((value, idx) => {
|
||||
const hidden = value.field.custom.hideFrom.viz;
|
||||
const display = value.display;
|
||||
return {
|
||||
label: display.title ?? '',
|
||||
color: display.color ?? FALLBACK_COLOR,
|
||||
yAxis: 1,
|
||||
disabled: hidden,
|
||||
getItemKey: () => (display.title ?? '') + idx,
|
||||
getDisplayValues: () => {
|
||||
const valuesToShow = legendOptions.values ?? [];
|
||||
let displayValues = [];
|
||||
|
||||
if (valuesToShow.includes(PieChartLegendValues.Value)) {
|
||||
displayValues.push({ numeric: display.numeric, text: formattedValueToString(display), title: 'Value' });
|
||||
}
|
||||
|
||||
if (valuesToShow.includes(PieChartLegendValues.Percent)) {
|
||||
const fractionOfTotal = hidden ? 0 : display.numeric / total;
|
||||
const percentOfTotal = fractionOfTotal * 100;
|
||||
|
||||
displayValues.push({
|
||||
numeric: fractionOfTotal,
|
||||
percent: percentOfTotal,
|
||||
text: hidden ? '-' : percentOfTotal.toFixed(0) + '%',
|
||||
title: valuesToShow.length > 1 ? 'Percent' : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
return displayValues;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<VizLegend
|
||||
items={legendItems}
|
||||
seriesVisibilityChangeBehavior={SeriesVisibilityChangeBehavior.Hide}
|
||||
placement={legendOptions.placement}
|
||||
displayMode={legendOptions.displayMode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function useSliceHighlightState() {
|
||||
const [highlightedTitle, setHighlightedTitle] = useState<string>();
|
||||
const { eventBus } = usePanelContext();
|
||||
|
||||
useEffect(() => {
|
||||
const setHighlightedSlice = (event: DataHoverEvent) => {
|
||||
setHighlightedTitle(event.payload.dataId);
|
||||
};
|
||||
|
||||
const resetHighlightedSlice = (event: DataHoverClearEvent) => {
|
||||
setHighlightedTitle(undefined);
|
||||
};
|
||||
|
||||
const subs = new Subscription()
|
||||
.add(eventBus.getStream(DataHoverEvent).subscribe({ next: setHighlightedSlice }))
|
||||
.add(eventBus.getStream(DataHoverClearEvent).subscribe({ next: resetHighlightedSlice }));
|
||||
|
||||
return () => {
|
||||
subs.unsubscribe();
|
||||
};
|
||||
}, [setHighlightedTitle, eventBus]);
|
||||
|
||||
return highlightedTitle;
|
||||
}
|
||||
|
||||
export const PieChartSvg: FC<PieChartSvgProps> = ({
|
||||
export const PieChart: FC<PieChartProps> = ({
|
||||
fieldDisplayValues,
|
||||
pieType,
|
||||
width,
|
@@ -1,35 +0,0 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { LegacyForms, InlineFormLabel, PieChartType } from '@grafana/ui';
|
||||
import { PanelEditorProps } from '@grafana/data';
|
||||
import { PieChartOptions } from './types';
|
||||
|
||||
const { Select } = LegacyForms;
|
||||
const labelWidth = 8;
|
||||
|
||||
const pieChartOptions = [
|
||||
{ value: PieChartType.Pie, label: 'Pie' },
|
||||
{ value: PieChartType.Donut, label: 'Donut' },
|
||||
];
|
||||
|
||||
export class PieChartOptionsBox extends PureComponent<PanelEditorProps<PieChartOptions>> {
|
||||
onPieTypeChange = (pieType: any) => this.props.onOptionsChange({ ...this.props.options, pieType: pieType.value });
|
||||
|
||||
render() {
|
||||
const { options } = this.props;
|
||||
const { pieType } = options;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel width={labelWidth}>Type</InlineFormLabel>
|
||||
<Select
|
||||
width={12}
|
||||
options={pieChartOptions}
|
||||
onChange={this.onPieTypeChange}
|
||||
value={pieChartOptions.find((option) => option.value === pieType)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,32 +1,150 @@
|
||||
import React from 'react';
|
||||
import { PieChart } from '@grafana/ui';
|
||||
import { PieChartOptions } from './types';
|
||||
import { PanelProps } from '@grafana/data';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
DataHoverClearEvent,
|
||||
DataHoverEvent,
|
||||
FALLBACK_COLOR,
|
||||
FieldDisplay,
|
||||
formattedValueToString,
|
||||
getFieldDisplayValues,
|
||||
PanelProps,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { PieChart } from './PieChart';
|
||||
|
||||
import { PieChartLegendOptions, PieChartLegendValues, PieChartOptions } from './types';
|
||||
import { Subscription } from 'rxjs';
|
||||
import {
|
||||
LegendDisplayMode,
|
||||
usePanelContext,
|
||||
useTheme2,
|
||||
VizLayout,
|
||||
VizLegend,
|
||||
VizLegendItem,
|
||||
SeriesVisibilityChangeBehavior,
|
||||
} from '@grafana/ui';
|
||||
|
||||
const defaultLegendOptions: PieChartLegendOptions = {
|
||||
displayMode: LegendDisplayMode.List,
|
||||
placement: 'right',
|
||||
calcs: [],
|
||||
values: [PieChartLegendValues.Percent],
|
||||
};
|
||||
|
||||
interface Props extends PanelProps<PieChartOptions> {}
|
||||
|
||||
export const PieChartPanel: React.FC<Props> = ({
|
||||
width,
|
||||
height,
|
||||
options,
|
||||
data,
|
||||
replaceVariables,
|
||||
fieldConfig,
|
||||
timeZone,
|
||||
}) => {
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export function PieChartPanel(props: Props) {
|
||||
const { data, timeZone, fieldConfig, replaceVariables, width, height, options } = props;
|
||||
|
||||
const theme = useTheme2();
|
||||
const highlightedTitle = useSliceHighlightState();
|
||||
const fieldDisplayValues = getFieldDisplayValues({
|
||||
fieldConfig,
|
||||
reduceOptions: options.reduceOptions,
|
||||
data: data.series,
|
||||
theme: theme,
|
||||
replaceVariables,
|
||||
timeZone,
|
||||
});
|
||||
|
||||
return (
|
||||
<PieChart
|
||||
width={width}
|
||||
height={height}
|
||||
timeZone={timeZone}
|
||||
fieldConfig={fieldConfig}
|
||||
reduceOptions={options.reduceOptions}
|
||||
replaceVariables={replaceVariables}
|
||||
data={data.series}
|
||||
pieType={options.pieType}
|
||||
displayLabels={options.displayLabels}
|
||||
legendOptions={options.legend}
|
||||
tooltipOptions={options.tooltip}
|
||||
<VizLayout width={width} height={height} legend={getLegend(props, fieldDisplayValues)}>
|
||||
{(vizWidth: number, vizHeight: number) => {
|
||||
return (
|
||||
<PieChart
|
||||
width={vizWidth}
|
||||
height={vizHeight}
|
||||
highlightedTitle={highlightedTitle}
|
||||
fieldDisplayValues={fieldDisplayValues}
|
||||
tooltipOptions={options.tooltip}
|
||||
pieType={options.pieType}
|
||||
displayLabels={options.displayLabels}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</VizLayout>
|
||||
);
|
||||
}
|
||||
|
||||
function getLegend(props: Props, displayValues: FieldDisplay[]) {
|
||||
const legendOptions = props.options.legend ?? defaultLegendOptions;
|
||||
|
||||
if (legendOptions.displayMode === LegendDisplayMode.Hidden) {
|
||||
return undefined;
|
||||
}
|
||||
const total = displayValues
|
||||
.filter((item) => {
|
||||
return !item.field.custom.hideFrom.viz;
|
||||
})
|
||||
.reduce((acc, item) => item.display.numeric + acc, 0);
|
||||
|
||||
const legendItems = displayValues.map<VizLegendItem>((value, idx) => {
|
||||
const hidden = value.field.custom.hideFrom.viz;
|
||||
const display = value.display;
|
||||
return {
|
||||
label: display.title ?? '',
|
||||
color: display.color ?? FALLBACK_COLOR,
|
||||
yAxis: 1,
|
||||
disabled: hidden,
|
||||
getItemKey: () => (display.title ?? '') + idx,
|
||||
getDisplayValues: () => {
|
||||
const valuesToShow = legendOptions.values ?? [];
|
||||
let displayValues = [];
|
||||
|
||||
if (valuesToShow.includes(PieChartLegendValues.Value)) {
|
||||
displayValues.push({ numeric: display.numeric, text: formattedValueToString(display), title: 'Value' });
|
||||
}
|
||||
|
||||
if (valuesToShow.includes(PieChartLegendValues.Percent)) {
|
||||
const fractionOfTotal = hidden ? 0 : display.numeric / total;
|
||||
const percentOfTotal = fractionOfTotal * 100;
|
||||
|
||||
displayValues.push({
|
||||
numeric: fractionOfTotal,
|
||||
percent: percentOfTotal,
|
||||
text: hidden ? '-' : percentOfTotal.toFixed(0) + '%',
|
||||
title: valuesToShow.length > 1 ? 'Percent' : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
return displayValues;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<VizLegend
|
||||
items={legendItems}
|
||||
seriesVisibilityChangeBehavior={SeriesVisibilityChangeBehavior.Hide}
|
||||
placement={legendOptions.placement}
|
||||
displayMode={legendOptions.displayMode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function useSliceHighlightState() {
|
||||
const [highlightedTitle, setHighlightedTitle] = useState<string>();
|
||||
const { eventBus } = usePanelContext();
|
||||
|
||||
useEffect(() => {
|
||||
const setHighlightedSlice = (event: DataHoverEvent) => {
|
||||
setHighlightedTitle(event.payload.dataId);
|
||||
};
|
||||
|
||||
const resetHighlightedSlice = (event: DataHoverClearEvent) => {
|
||||
setHighlightedTitle(undefined);
|
||||
};
|
||||
|
||||
const subs = new Subscription()
|
||||
.add(eventBus.getStream(DataHoverEvent).subscribe({ next: setHighlightedSlice }))
|
||||
.add(eventBus.getStream(DataHoverClearEvent).subscribe({ next: resetHighlightedSlice }));
|
||||
|
||||
return () => {
|
||||
subs.unsubscribe();
|
||||
};
|
||||
}, [setHighlightedTitle, eventBus]);
|
||||
|
||||
return highlightedTitle;
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { FieldColorModeId, FieldConfigProperty, FieldMatcherID, PanelModel } from '@grafana/data';
|
||||
import { LegendDisplayMode, PieChartLabels } from '@grafana/ui';
|
||||
import { LegendDisplayMode } from '@grafana/ui';
|
||||
import { PieChartPanelChangedHandler } from './migrations';
|
||||
import { PieChartLabels } from './types';
|
||||
|
||||
describe('PieChart -> PieChartV2 migrations', () => {
|
||||
it('only migrates old piechart', () => {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { FieldColorModeId, FieldConfigProperty, FieldMatcherID, PanelModel } from '@grafana/data';
|
||||
import { LegendDisplayMode, PieChartLabels, PieChartLegendValues, PieChartType } from '@grafana/ui';
|
||||
import { PieChartOptions } from './types';
|
||||
import { LegendDisplayMode } from '@grafana/ui';
|
||||
import { PieChartOptions, PieChartLabels, PieChartLegendValues, PieChartType } from './types';
|
||||
|
||||
export const PieChartPanelChangedHandler = (
|
||||
panel: PanelModel<Partial<PieChartOptions>> | any,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { FieldColorModeId, FieldConfigProperty, PanelPlugin, ReducerID, standardEditorsRegistry } from '@grafana/data';
|
||||
import { PieChartPanel } from './PieChartPanel';
|
||||
import { PieChartOptions } from './types';
|
||||
import { LegendDisplayMode, PieChartType, PieChartLabels, PieChartLegendValues } from '@grafana/ui';
|
||||
import { PieChartOptions, PieChartType, PieChartLabels, PieChartLegendValues } from './types';
|
||||
import { LegendDisplayMode } from '@grafana/ui';
|
||||
import { PieChartPanelChangedHandler } from './migrations';
|
||||
import { addHideFrom } from '../timeseries/config';
|
||||
|
||||
|
@@ -1,10 +1,37 @@
|
||||
import {
|
||||
PieChartType,
|
||||
SingleStatBaseOptions,
|
||||
PieChartLabels,
|
||||
PieChartLegendOptions,
|
||||
VizTooltipOptions,
|
||||
} from '@grafana/ui';
|
||||
import { SingleStatBaseOptions, VizLegendOptions, VizTooltipOptions } from '@grafana/ui';
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export enum PieChartType {
|
||||
Pie = 'pie',
|
||||
Donut = 'donut',
|
||||
}
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export enum PieChartLegendValues {
|
||||
Value = 'value',
|
||||
Percent = 'percent',
|
||||
}
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export enum PieChartLabels {
|
||||
Name = 'name',
|
||||
Value = 'value',
|
||||
Percent = 'percent',
|
||||
}
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export interface PieChartLegendOptions extends VizLegendOptions {
|
||||
values: PieChartLegendValues[];
|
||||
}
|
||||
|
||||
export interface PieChartOptions extends SingleStatBaseOptions {
|
||||
pieType: PieChartType;
|
||||
displayLabels: PieChartLabels[];
|
||||
|
Reference in New Issue
Block a user