diff --git a/devenv/dev-dashboards/panel-piechart/panel_test_piechart.json b/devenv/dev-dashboards/panel-piechart/panel_test_piechart.json index 08e94e66e2e..37e6c6f930a 100644 --- a/devenv/dev-dashboards/panel-piechart/panel_test_piechart.json +++ b/devenv/dev-dashboards/panel-piechart/panel_test_piechart.json @@ -15,7 +15,7 @@ "editable": true, "gnetId": null, "graphTooltip": 0, - "id": 231, + "id": 479, "links": [], "panels": [ { @@ -49,23 +49,34 @@ "h": 14, "w": 8, "x": 0, - "y": 3 + "y": 0 }, "id": 2, "options": { + "displayLabels": [ + "name" + ], "labelOptions": { "showName": true, "showPercent": false, "showValue": false }, + "legend": { + "displayColumns": [], + "displayMode": "list", + "placement": "right" + }, "pieType": "donut", "reduceOptions": { - "calcs": ["mean"], + "calcs": [ + "mean" + ], "fields": "", "values": false }, "showLegend": true, - "strokeWidth": 1 + "strokeWidth": 1, + "text": {} }, "pluginVersion": "7.3.0-pre", "targets": [ @@ -112,23 +123,37 @@ "h": 14, "w": 8, "x": 8, - "y": 3 + "y": 0 }, "id": 11, "options": { + "displayLabels": [ + "name", + "percent" + ], "labelOptions": { "showName": true, "showPercent": true, "showValue": false }, + "legend": { + "displayColumns": [ + "percent" + ], + "displayMode": "list", + "placement": "right" + }, "pieType": "pie", "reduceOptions": { - "calcs": ["mean"], + "calcs": [ + "mean" + ], "fields": "", "values": false }, "showLegend": true, - "strokeWidth": 1 + "strokeWidth": 1, + "text": {} }, "pluginVersion": "7.3.0-pre", "targets": [ @@ -173,23 +198,34 @@ "h": 14, "w": 8, "x": 16, - "y": 3 + "y": 0 }, "id": 8, "options": { + "displayLabels": [ + "name" + ], "labelOptions": { "showName": true, "showPercent": false, "showValue": false }, + "legend": { + "displayColumns": [], + "displayMode": "hidden", + "placement": "right" + }, "pieType": "pie", "reduceOptions": { - "calcs": ["mean"], + "calcs": [ + "mean" + ], "fields": "", "values": false }, "showLegend": false, - "strokeWidth": 1 + "strokeWidth": 1, + "text": {} }, "pluginVersion": "7.3.0-pre", "targets": [ @@ -202,7 +238,7 @@ ], "timeFrom": null, "timeShift": null, - "title": "Name", + "title": "Name & No legend", "type": "piechart" }, { @@ -234,23 +270,36 @@ "h": 9, "w": 5, "x": 0, - "y": 17 + "y": 14 }, "id": 3, "options": { + "displayLabels": [ + "percent" + ], "labelOptions": { "showName": false, "showPercent": true, "showValue": false }, + "legend": { + "displayColumns": [ + "percent" + ], + "displayMode": "table", + "placement": "bottom" + }, "pieType": "pie", "reduceOptions": { - "calcs": ["mean"], + "calcs": [ + "mean" + ], "fields": "", "values": false }, "showLegend": false, - "strokeWidth": 1 + "strokeWidth": 1, + "text": {} }, "pluginVersion": "7.3.0-pre", "targets": [ @@ -295,23 +344,36 @@ "h": 9, "w": 5, "x": 5, - "y": 17 + "y": 14 }, "id": 9, "options": { + "displayLabels": [ + "value" + ], "labelOptions": { "showName": false, "showPercent": false, "showValue": true }, + "legend": { + "displayColumns": [ + "value" + ], + "displayMode": "table", + "placement": "bottom" + }, "pieType": "pie", "reduceOptions": { - "calcs": ["mean"], + "calcs": [ + "mean" + ], "fields": "", "values": false }, "showLegend": false, - "strokeWidth": 1 + "strokeWidth": 1, + "text": {} }, "pluginVersion": "7.3.0-pre", "targets": [ @@ -356,23 +418,34 @@ "h": 9, "w": 6, "x": 10, - "y": 17 + "y": 14 }, "id": 6, "options": { + "displayLabels": [ + "name" + ], "labelOptions": { "showName": true, "showPercent": false, "showValue": false }, - "pieType": "donut", + "legend": { + "displayColumns": [], + "displayMode": "list", + "placement": "bottom" + }, + "pieType": "pie", "reduceOptions": { - "calcs": ["mean"], + "calcs": [ + "mean" + ], "fields": "", "values": false }, "showLegend": false, - "strokeWidth": 1 + "strokeWidth": 1, + "text": {} }, "pluginVersion": "7.3.0-pre", "targets": [ @@ -417,23 +490,32 @@ "h": 9, "w": 8, "x": 16, - "y": 17 + "y": 14 }, "id": 10, "options": { + "displayLabels": [], "labelOptions": { "showName": true, "showPercent": false, "showValue": false }, + "legend": { + "displayColumns": ["percent", "value"], + "displayMode": "table", + "placement": "bottom" + }, "pieType": "pie", "reduceOptions": { - "calcs": ["mean"], + "calcs": [ + "mean" + ], "fields": "", "values": false }, "showLegend": false, - "strokeWidth": 1 + "strokeWidth": 1, + "text": {} }, "pluginVersion": "7.3.0-pre", "targets": [ @@ -450,9 +532,12 @@ "type": "piechart" } ], - "schemaVersion": 26, + "schemaVersion": 27, "style": "dark", - "tags": ["gdev", "panel-tests"], + "tags": [ + "gdev", + "panel-tests" + ], "templating": { "list": [] }, @@ -464,5 +549,5 @@ "timezone": "", "title": "Panel Tests - Pie chart", "uid": "lVE-2YFMz", - "version": 1 -} + "version": 9 +} \ No newline at end of file diff --git a/packages/grafana-ui/src/components/OptionsUI/multiSelect.tsx b/packages/grafana-ui/src/components/OptionsUI/multiSelect.tsx index 258ed7b0316..627539fd2dc 100644 --- a/packages/grafana-ui/src/components/OptionsUI/multiSelect.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/multiSelect.tsx @@ -9,6 +9,10 @@ interface State { type Props = FieldConfigEditorProps>; +/** + * MultiSelect for options UI + * @alpha + */ export class MultiSelectValueEditor extends React.PureComponent, State> { state: State = { isLoading: true, diff --git a/packages/grafana-ui/src/components/PieChart/PieChart.story.tsx b/packages/grafana-ui/src/components/PieChart/PieChart.story.tsx index 75893609208..41e9654d5d0 100644 --- a/packages/grafana-ui/src/components/PieChart/PieChart.story.tsx +++ b/packages/grafana-ui/src/components/PieChart/PieChart.story.tsx @@ -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 ; + return ; }; 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 ( - - ); + return ; }; diff --git a/packages/grafana-ui/src/components/PieChart/PieChart.tsx b/packages/grafana-ui/src/components/PieChart/PieChart.tsx index 89e8f3f8aa0..7f949316ee8 100644 --- a/packages/grafana-ui/src/components/PieChart/PieChart.tsx +++ b/packages/grafana-ui/src/components/PieChart/PieChart.tsx @@ -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 = ({ 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((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 = ({ width, height, useGradients = true, - labelOptions = { showName: true }, + displayLabels = [], }) => { const theme = useTheme(); const componentInstanceId = useComponentInstanceId('PieChart'); @@ -106,7 +137,7 @@ export const PieChartSvg: FC = ({ }); }; - 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 = ({ arc={arc} outerRadius={layout.outerRadius} innerRadius={layout.innerRadius} - labelOptions={labelOptions} + displayLabels={displayLabels} total={total} /> )} @@ -183,9 +214,9 @@ const PieLabel: FC<{ arc: PieArcDatum; 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) && ( {arc.data.title} )} - {labelOptions.showValue && ( + {displayLabels.includes(PieChartLabels.Value) && ( {formattedValueToString(arc.data)} )} - {labelOptions.showPercent && ( + {displayLabels.includes(PieChartLabels.Percent) && ( {((arc.data.numeric / total) * 100).toFixed(0) + '%'} diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendList.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendList.tsx index a96a313ef0d..92d7988c8b7 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendList.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendList.tsx @@ -39,7 +39,7 @@ export const VizLegendList: React.FunctionComponent = ({ return (
- +
); } diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx index dfdf4942ab6..a9195c1f977 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx @@ -62,6 +62,7 @@ const getStyles = (theme: GrafanaTheme) => ({ display: flex; white-space: nowrap; align-items: center; + flex-grow: 1; `, value: css` text-align: right; diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendStatsList.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendStatsList.tsx index 78cfd42ca0b..d8427ee2ef6 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendStatsList.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendStatsList.tsx @@ -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 ( -
- {stat.title && `${capitalize(stat.title)}:`} {formattedValueToString(stat)} -
- ); -}; - -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 } />; + + return ( + ( +
+ {stat.title && `${capitalize(stat.title)}:`} {formattedValueToString(stat)} +
+ )} + /> + ); }; +const getStyles = () => ({ + list: css` + flex-grow: 1; + text-align: right; + `, + item: css` + margin-left: 8px; + `, +}); + VizLegendStatsList.displayName = 'VizLegendStatsList'; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 2517906ece6..ae8ce3dd287 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -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'; diff --git a/public/app/plugins/panel/piechart/PieChartPanel.tsx b/public/app/plugins/panel/piechart/PieChartPanel.tsx index cee02bb34a7..08df09bcf5f 100644 --- a/public/app/plugins/panel/piechart/PieChartPanel.tsx +++ b/public/app/plugins/panel/piechart/PieChartPanel.tsx @@ -25,7 +25,8 @@ export class PieChartPanel extends PureComponent { height={height} values={values} pieType={options.pieType} - labelOptions={options.labelOptions} + displayLabels={options.displayLabels} + legendOptions={options.legend} /> ); } diff --git a/public/app/plugins/panel/piechart/module.tsx b/public/app/plugins/panel/piechart/module.tsx index 1c276c7e7ac..54161a6557c 100644 --- a/public/app/plugins/panel/piechart/module.tsx +++ b/public/app/plugins/panel/piechart/module.tsx @@ -2,7 +2,8 @@ import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/dat import { PieChartPanel } from './PieChartPanel'; import { PieChartOptions } from './types'; import { addStandardDataReduceOptions } from '../stat/types'; -import { PieChartType } from '@grafana/ui'; +import { LegendDisplayMode, PieChartType } from '@grafana/ui'; +import { LegendColumns, PieChartLabels } from '@grafana/ui/src/components/PieChart/PieChart'; export const plugin = new PanelPlugin(PieChartPanel) .useFieldConfig({ @@ -35,19 +36,53 @@ export const plugin = new PanelPlugin(PieChartPanel) }, defaultValue: PieChartType.Pie, }) - .addBooleanSwitch({ - name: 'Show name', - path: 'labelOptions.showName', - defaultValue: true, + .addMultiSelect({ + name: 'Labels', + path: 'displayLabels', + description: 'Select the labels to be displayed in the pie chart', + settings: { + options: [ + { value: PieChartLabels.Percent, label: 'Percent' }, + { value: PieChartLabels.Name, label: 'Name' }, + { value: PieChartLabels.Value, label: 'Value' }, + ], + }, }) - .addBooleanSwitch({ - name: 'Show value', - path: 'labelOptions.showValue', - defaultValue: false, + .addRadio({ + path: 'legend.displayMode', + name: 'Legend mode', + description: '', + defaultValue: LegendDisplayMode.List, + settings: { + options: [ + { value: LegendDisplayMode.List, label: 'List' }, + { value: LegendDisplayMode.Table, label: 'Table' }, + { value: LegendDisplayMode.Hidden, label: 'Hidden' }, + ], + }, }) - .addBooleanSwitch({ - name: 'Show percent', - path: 'labelOptions.showPercent', - defaultValue: false, + .addRadio({ + path: 'legend.placement', + name: 'Legend placement', + description: '', + defaultValue: 'right', + settings: { + options: [ + { value: 'bottom', label: 'Bottom' }, + { value: 'right', label: 'Right' }, + ], + }, + showIf: (c) => c.legend.displayMode !== LegendDisplayMode.Hidden, + }) + .addMultiSelect({ + name: 'Legend values', + path: 'legend.displayColumns', + settings: { + options: [ + { value: LegendColumns.Percent, label: 'Percent' }, + { value: LegendColumns.Value, label: 'Value' }, + ], + }, + showIf: (c) => c.legend.displayMode !== LegendDisplayMode.Hidden, }); }); diff --git a/public/app/plugins/panel/piechart/types.ts b/public/app/plugins/panel/piechart/types.ts index f30c2b080c5..bcc4dd36401 100644 --- a/public/app/plugins/panel/piechart/types.ts +++ b/public/app/plugins/panel/piechart/types.ts @@ -1,6 +1,7 @@ -import { PieChartType, SingleStatBaseOptions, PieChartLabelOptions } from '@grafana/ui'; +import { PieChartType, SingleStatBaseOptions, PieChartLabels, PieChartLegendOptions } from '@grafana/ui'; export interface PieChartOptions extends SingleStatBaseOptions { pieType: PieChartType; - labelOptions: PieChartLabelOptions; + displayLabels: PieChartLabels[]; + legend: PieChartLegendOptions; }