PieChart: Improve piechart legend and options (#31446)

* Add percent of total to piechart legend

* Remove defaults

* Add label selector

* Fix multiselect option ui

* Add percent of total to piechart legend

* Add label selector

* add multiselect options ui

* change how pie chart labels are displayed

* Fixed right aligned values in legend

* added titles to display values so they show in table mode

* Move legend display value options to below other options

* Add addMultiSelect method to ui builder

* Use addMultiSelect on builder

* Use multiselect for the legend columns and update the panel test dashboard

* Remove explicit typing on addMultiselect and remove non existing properties from piechart story

* Add release tag

Co-authored-by: Torkel Ödegaard <torkel@grafana.org>
This commit is contained in:
Oscar Kilhed
2021-03-01 11:18:24 +01:00
committed by GitHub
parent c360ac7278
commit 10def28989
11 changed files with 250 additions and 92 deletions

View File

@@ -9,6 +9,10 @@ interface State<T> {
type Props<T> = FieldConfigEditorProps<T[], SelectFieldConfigSettings<T>>;
/**
* MultiSelect for options UI
* @alpha
*/
export class MultiSelectValueEditor<T> extends React.PureComponent<Props<T>, State<T>> {
state: State<T> = {
isLoading: true,

View File

@@ -28,23 +28,13 @@ const getKnobs = () => {
};
export const basic = () => {
const { datapoints, pieType, width, height, showLabelName, showLabelPercent, showLabelValue } = getKnobs();
const labelOptions = { showName: showLabelName, showPercent: showLabelPercent, showValue: showLabelValue };
const { datapoints, pieType, width, height } = getKnobs();
return <PieChart width={width} height={height} values={datapoints} pieType={pieType} labelOptions={labelOptions} />;
return <PieChart width={width} height={height} values={datapoints} pieType={pieType} />;
};
export const donut = () => {
const { datapoints, width, height, showLabelName, showLabelPercent, showLabelValue } = getKnobs();
const labelOptions = { showName: showLabelName, showPercent: showLabelPercent, showValue: showLabelValue };
const { datapoints, width, height } = getKnobs();
return (
<PieChart
width={width}
height={height}
values={datapoints}
pieType={PieChartType.Donut}
labelOptions={labelOptions}
/>
);
return <PieChart width={width} height={height} values={datapoints} pieType={PieChartType.Donut} />;
};

View File

@@ -13,16 +13,27 @@ import { VizLegend, VizLegendItem } from '..';
import { VizLayout } from '../VizLayout/VizLayout';
import { LegendDisplayMode, VizLegendOptions } from '../VizLegend/types';
export enum PieChartLabels {
Name = 'name',
Value = 'value',
Percent = 'percent',
}
export enum LegendColumns {
Value = 'value',
Percent = 'percent',
}
interface SvgProps {
height: number;
width: number;
values: DisplayValue[];
pieType: PieChartType;
labelOptions?: PieChartLabelOptions;
displayLabels?: PieChartLabels[];
useGradients?: boolean;
}
export interface Props extends SvgProps {
legendOptions?: VizLegendOptions;
legendOptions?: PieChartLegendOptions;
}
export enum PieChartType {
@@ -30,29 +41,49 @@ export enum PieChartType {
Donut = 'donut',
}
export interface PieChartLabelOptions {
showName?: boolean;
showValue?: boolean;
showPercent?: boolean;
export interface PieChartLegendOptions extends VizLegendOptions {
displayColumns: LegendColumns[];
}
const defaultLegendOptions: VizLegendOptions = {
const defaultLegendOptions: PieChartLegendOptions = {
displayMode: LegendDisplayMode.List,
placement: 'right',
calcs: [],
displayColumns: [LegendColumns.Percent],
};
export const PieChart: FC<Props> = ({ values, legendOptions = defaultLegendOptions, width, height, ...restProps }) => {
const getLegend = (values: DisplayValue[], legendOptions: VizLegendOptions) => {
const getLegend = (values: DisplayValue[], legendOptions: PieChartLegendOptions) => {
if (legendOptions.displayMode === LegendDisplayMode.Hidden) {
return undefined;
}
const total = values.reduce((acc, item) => item.numeric + acc, 0);
const legendItems = values.map<VizLegendItem>((value) => {
return {
label: value.title ?? '',
color: value.color ?? FALLBACK_COLOR,
yAxis: 1,
getDisplayValues: () => {
let displayValues = [];
if (legendOptions.displayColumns.includes(LegendColumns.Value)) {
displayValues.push({ numeric: value.numeric, text: formattedValueToString(value), title: 'Value' });
}
if (legendOptions.displayColumns.includes(LegendColumns.Percent)) {
const fractionOfTotal = value.numeric / total;
const percentOfTotal = fractionOfTotal * 100;
displayValues.push({
numeric: fractionOfTotal,
percent: percentOfTotal,
text: percentOfTotal.toFixed(0) + '%',
title: 'Percent',
});
}
return displayValues;
},
};
});
@@ -76,7 +107,7 @@ export const PieChartSvg: FC<SvgProps> = ({
width,
height,
useGradients = true,
labelOptions = { showName: true },
displayLabels = [],
}) => {
const theme = useTheme();
const componentInstanceId = useComponentInstanceId('PieChart');
@@ -106,7 +137,7 @@ export const PieChartSvg: FC<SvgProps> = ({
});
};
const showLabel = labelOptions.showName || labelOptions.showPercent || labelOptions.showValue;
const showLabel = displayLabels.length > 0;
const total = values.reduce((acc, item) => item.numeric + acc, 0);
const layout = getPieLayout(width, height, pieType);
@@ -159,7 +190,7 @@ export const PieChartSvg: FC<SvgProps> = ({
arc={arc}
outerRadius={layout.outerRadius}
innerRadius={layout.innerRadius}
labelOptions={labelOptions}
displayLabels={displayLabels}
total={total}
/>
)}
@@ -183,9 +214,9 @@ const PieLabel: FC<{
arc: PieArcDatum<DisplayValue>;
outerRadius: number;
innerRadius: number;
labelOptions: PieChartLabelOptions;
displayLabels: PieChartLabels[];
total: number;
}> = ({ arc, outerRadius, innerRadius, labelOptions, total }) => {
}> = ({ arc, outerRadius, innerRadius, displayLabels, total }) => {
const labelRadius = innerRadius === 0 ? outerRadius / 6 : innerRadius;
const [labelX, labelY] = getLabelPos(arc, outerRadius, labelRadius);
const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.3;
@@ -194,7 +225,7 @@ const PieLabel: FC<{
return null;
}
let labelFontSize = labelOptions.showName
let labelFontSize = displayLabels.includes(PieChartLabels.Name)
? Math.min(Math.max((outerRadius / 150) * 14, 12), 30)
: Math.min(Math.max((outerRadius / 100) * 14, 12), 36);
@@ -209,17 +240,17 @@ const PieLabel: FC<{
textAnchor="middle"
pointerEvents="none"
>
{labelOptions.showName && (
{displayLabels.includes(PieChartLabels.Name) && (
<tspan x={labelX} dy="1.2em">
{arc.data.title}
</tspan>
)}
{labelOptions.showValue && (
{displayLabels.includes(PieChartLabels.Value) && (
<tspan x={labelX} dy="1.2em">
{formattedValueToString(arc.data)}
</tspan>
)}
{labelOptions.showPercent && (
{displayLabels.includes(PieChartLabels.Percent) && (
<tspan x={labelX} dy="1.2em">
{((arc.data.numeric / total) * 100).toFixed(0) + '%'}
</tspan>

View File

@@ -39,7 +39,7 @@ export const VizLegendList: React.FunctionComponent<Props> = ({
return (
<div className={cx(styles.rightWrapper, className)}>
<List items={items} renderItem={renderItem} getItemKey={getItemKey} className={className} />
<List items={items} renderItem={renderItem} getItemKey={getItemKey} />
</div>
);
}

View File

@@ -62,6 +62,7 @@ const getStyles = (theme: GrafanaTheme) => ({
display: flex;
white-space: nowrap;
align-items: center;
flex-grow: 1;
`,
value: css`
text-align: right;

View File

@@ -3,29 +3,39 @@ import { InlineList } from '../List/InlineList';
import { css } from 'emotion';
import { DisplayValue, formattedValueToString } from '@grafana/data';
import capitalize from 'lodash/capitalize';
const VizLegendItemStat: React.FunctionComponent<{ stat: DisplayValue }> = ({ stat }) => {
const styles = css`
margin-left: 8px;
`;
return (
<div className={styles}>
{stat.title && `${capitalize(stat.title)}:`} {formattedValueToString(stat)}
</div>
);
};
VizLegendItemStat.displayName = 'VizLegendItemStat';
import { useStyles } from '../../themes/ThemeContext';
/**
* @internal
*/
export const VizLegendStatsList: React.FunctionComponent<{ stats: DisplayValue[] }> = ({ stats }) => {
const styles = useStyles(getStyles);
if (stats.length === 0) {
return null;
}
return <InlineList items={stats} renderItem={(stat) => <VizLegendItemStat stat={stat} />} />;
return (
<InlineList
className={styles.list}
items={stats}
renderItem={(stat) => (
<div className={styles.item}>
{stat.title && `${capitalize(stat.title)}:`} {formattedValueToString(stat)}
</div>
)}
/>
);
};
const getStyles = () => ({
list: css`
flex-grow: 1;
text-align: right;
`,
item: css`
margin-left: 8px;
`,
});
VizLegendStatsList.displayName = 'VizLegendStatsList';

View File

@@ -17,7 +17,7 @@ export { LoadingPlaceholder, LoadingPlaceholderProps } from './LoadingPlaceholde
export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker';
export { SeriesColorPickerPopover, SeriesColorPickerPopoverWithTheme } from './ColorPicker/SeriesColorPickerPopover';
export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult';
export { PieChart, PieChartType, PieChartLabelOptions } from './PieChart/PieChart';
export { PieChart, PieChartType, PieChartLabels, PieChartLegendOptions } from './PieChart/PieChart';
export { UnitPicker } from './UnitPicker/UnitPicker';
export { StatsPicker } from './StatsPicker/StatsPicker';
export { RefreshPicker, defaultIntervals } from './RefreshPicker/RefreshPicker';