123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- import React, { useEffect, useState } from 'react';
- import { Subscription } from 'rxjs';
- import {
- DataHoverClearEvent,
- DataHoverEvent,
- FALLBACK_COLOR,
- FieldDisplay,
- formattedValueToString,
- getFieldDisplayValues,
- PanelProps,
- } from '@grafana/data';
- import { PanelDataErrorView } from '@grafana/runtime';
- import { LegendDisplayMode } from '@grafana/schema';
- import {
- SeriesVisibilityChangeBehavior,
- usePanelContext,
- useTheme2,
- VizLayout,
- VizLegend,
- VizLegendItem,
- } from '@grafana/ui';
- import { PieChart } from './PieChart';
- import { PieChartLegendOptions, PieChartLegendValues, PieChartOptions } from './types';
- import { filterDisplayItems, sumDisplayItemsReducer } from './utils';
- const defaultLegendOptions: PieChartLegendOptions = {
- displayMode: LegendDisplayMode.List,
- placement: 'right',
- calcs: [],
- values: [PieChartLegendValues.Percent],
- };
- interface Props extends PanelProps<PieChartOptions> {}
- /**
- * @beta
- */
- export function PieChartPanel(props: Props) {
- const { data, timeZone, fieldConfig, replaceVariables, width, height, options, id } = props;
- const theme = useTheme2();
- const highlightedTitle = useSliceHighlightState();
- const fieldDisplayValues = getFieldDisplayValues({
- fieldConfig,
- reduceOptions: options.reduceOptions,
- data: data.series,
- theme: theme,
- replaceVariables,
- timeZone,
- });
- if (!hasFrames(fieldDisplayValues)) {
- return <PanelDataErrorView panelId={id} fieldConfig={fieldConfig} data={data} />;
- }
- return (
- <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(filterDisplayItems).reduce(sumDisplayItemsReducer, 0);
- const legendItems = displayValues
- // Since the pie chart is always sorted, let's sort the legend as well.
- .sort((a, b) => {
- if (isNaN(a.display.numeric)) {
- return 1;
- } else if (isNaN(b.display.numeric)) {
- return -1;
- } else {
- return b.display.numeric - a.display.numeric;
- }
- })
- .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 || isNaN(fractionOfTotal)
- ? props.fieldConfig.defaults.noValue ?? '-'
- : percentOfTotal.toFixed(value.field.decimals ?? 0) + '%',
- title: valuesToShow.length > 1 ? 'Percent' : '',
- });
- }
- return displayValues;
- },
- };
- });
- return (
- <VizLegend
- items={legendItems}
- seriesVisibilityChangeBehavior={SeriesVisibilityChangeBehavior.Hide}
- placement={legendOptions.placement}
- displayMode={legendOptions.displayMode}
- />
- );
- }
- function hasFrames(fieldDisplayValues: FieldDisplay[]) {
- return fieldDisplayValues.some((fd) => fd.view?.dataFrame.length);
- }
- 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();
- subs.add(eventBus.getStream(DataHoverEvent).subscribe({ next: setHighlightedSlice }));
- subs.add(eventBus.getStream(DataHoverClearEvent).subscribe({ next: resetHighlightedSlice }));
- return () => {
- subs.unsubscribe();
- };
- }, [setHighlightedTitle, eventBus]);
- return highlightedTitle;
- }
|