PieChart: Unify tooltip to look the way it looks in TimeSeries (#33032)

* feat(piechart): align styles between piechart and graph

* feat(piechart): introduce tooltip options to panel and visualisation

* feat(piechart): get tooltip options working

* feat(piechart): add SeriesTable to visx TooltipInPortal

* refactor(piechart): move getTooltipData out of PieSlice

* docs(piechart): fix storybook story errors

* feat(viztooltip): initial commit of common tooltip types and components

* refactor(viztooltip): rename type as enum and update usage

* refactor(viztooltip): move chart.tooltip into viztooltip and fix imports and typings

* refactor(viztooltip): update import paths and names where used

* docs(infotooltip): fix story import paths

* docs(piechart): fix typings in story

* docs(viztooltip): add public annotations to exported components and types
This commit is contained in:
Jack Westbrook 2021-04-20 09:45:41 +02:00 committed by GitHub
parent 5f54f2dc00
commit c2953f3a06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 314 additions and 166 deletions

View File

@ -23,7 +23,7 @@ TableSortByFieldState: {
desc?: bool desc?: bool
} @cuetsy(targetType="interface") } @cuetsy(targetType="interface")
TooltipMode: "single" | "multi" | "none" @cuetsy(targetType="type") TooltipDisplayMode: "single" | "multi" | "none" @cuetsy(targetType="enum")
FieldTextAlignment: "auto" | "left" | "right" | "center" @cuetsy(targetType="type") FieldTextAlignment: "auto" | "left" | "right" | "center" @cuetsy(targetType="type")
AxisPlacement: "auto" | "top" | "right" | "bottom" | "left" | "hidden" @cuetsy(targetType="enum") AxisPlacement: "auto" | "top" | "right" | "bottom" | "left" | "hidden" @cuetsy(targetType="enum")
PointVisibility: "auto" | "never" | "always" @cuetsy(targetType="enum") PointVisibility: "auto" | "never" | "always" @cuetsy(targetType="enum")
@ -72,9 +72,6 @@ HideSeriesConfig: {
} @cuetsy(targetType="interface") } @cuetsy(targetType="interface")
LegendPlacement: "bottom" | "right" @cuetsy(targetType="type") LegendPlacement: "bottom" | "right" @cuetsy(targetType="type")
LegendDisplayMode: "list" | "table" | "hidden" @cuetsy(targetType="enum") LegendDisplayMode: "list" | "table" | "hidden" @cuetsy(targetType="enum")
GraphTooltipOptions: {
mode: TooltipMode
} @cuetsy(targetType="interface")
TableFieldOptions: { TableFieldOptions: {
width?: number width?: number
align: FieldTextAlignment | *"auto" align: FieldTextAlignment | *"auto"
@ -91,3 +88,6 @@ VizLegendOptions: {
placement: LegendPlacement placement: LegendPlacement
calcs: [string] calcs: [string]
} @cuetsy(targetType="interface") } @cuetsy(targetType="interface")
VizTooltipOptions: {
mode: TooltipDisplayMode
} @cuetsy(targetType="interface")

View File

@ -1,7 +0,0 @@
import { Tooltip } from './Tooltip';
const Chart = {
Tooltip,
};
export default Chart;

View File

@ -1,3 +0,0 @@
package grafanaschema
TooltipMode: "single" | "multi" | "none" @cuetsy(targetType="type")

View File

@ -1,6 +0,0 @@
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NOTE: This file will be auto generated from models.cue
// It is currenty hand written but will serve as the target for cuetsy
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
export type TooltipMode = 'single' | 'multi' | 'none';

View File

@ -1,11 +1,9 @@
import React from 'react'; import React from 'react';
import { Graph } from '@grafana/ui'; import { Graph } from '@grafana/ui';
import Chart from '../Chart';
import { dateTime, ArrayVector, FieldType, GraphSeriesXY, FieldColorModeId } from '@grafana/data'; import { dateTime, ArrayVector, FieldType, GraphSeriesXY, FieldColorModeId } from '@grafana/data';
import { Story } from '@storybook/react'; import { Story } from '@storybook/react';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { TooltipContentProps } from '../Chart/Tooltip'; import { VizTooltip, TooltipDisplayMode, VizTooltipContentProps } from '../VizTooltip';
import { TooltipMode } from '../Chart/models.gen';
import { JSONFormatter } from '../JSONFormatter/JSONFormatter'; import { JSONFormatter } from '../JSONFormatter/JSONFormatter';
import { GraphProps } from './Graph'; import { GraphProps } from './Graph';
@ -114,15 +112,15 @@ export default {
}, },
}; };
export const WithTooltip: Story<GraphProps & { tooltipMode: TooltipMode }> = ({ tooltipMode, ...args }) => { export const WithTooltip: Story<GraphProps & { tooltipMode: TooltipDisplayMode }> = ({ tooltipMode, ...args }) => {
return ( return (
<Graph {...args}> <Graph {...args}>
<Chart.Tooltip mode={tooltipMode} /> <VizTooltip mode={tooltipMode} />
</Graph> </Graph>
); );
}; };
const CustomGraphTooltip = ({ activeDimensions }: TooltipContentProps) => { const CustomGraphTooltip = ({ activeDimensions }: VizTooltipContentProps) => {
return ( return (
<div style={{ height: '200px' }}> <div style={{ height: '200px' }}>
<div>Showing currently active active dimensions:</div> <div>Showing currently active active dimensions:</div>
@ -131,10 +129,13 @@ const CustomGraphTooltip = ({ activeDimensions }: TooltipContentProps) => {
); );
}; };
export const WithCustomTooltip: Story<GraphProps & { tooltipMode: TooltipMode }> = ({ tooltipMode, ...args }) => { export const WithCustomTooltip: Story<GraphProps & { tooltipMode: TooltipDisplayMode }> = ({
tooltipMode,
...args
}) => {
return ( return (
<Graph {...args}> <Graph {...args}>
<Chart.Tooltip mode={tooltipMode} tooltipComponent={CustomGraphTooltip} /> <VizTooltip mode={tooltipMode} tooltipComponent={CustomGraphTooltip} />
</Graph> </Graph>
); );
}; };

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import Graph from './Graph'; import Graph from './Graph';
import Chart from '../Chart'; import { VizTooltip, TooltipDisplayMode } from '../VizTooltip';
import { GraphSeriesXY, FieldType, ArrayVector, dateTime, FieldColorModeId } from '@grafana/data'; import { GraphSeriesXY, FieldType, ArrayVector, dateTime, FieldColorModeId } from '@grafana/data';
const series: GraphSeriesXY[] = [ const series: GraphSeriesXY[] = [
@ -92,7 +92,7 @@ describe('Graph', () => {
it("doesn't render tooltip when not hovering over a datapoint", () => { it("doesn't render tooltip when not hovering over a datapoint", () => {
const graphWithTooltip = ( const graphWithTooltip = (
<Graph {...mockGraphProps()}> <Graph {...mockGraphProps()}>
<Chart.Tooltip mode="single" /> <VizTooltip mode={TooltipDisplayMode.Single} />
</Graph> </Graph>
); );
@ -105,7 +105,7 @@ describe('Graph', () => {
// Given // Given
const graphWithTooltip = ( const graphWithTooltip = (
<Graph {...mockGraphProps()}> <Graph {...mockGraphProps()}>
<Chart.Tooltip mode="single" /> <VizTooltip mode={TooltipDisplayMode.Single} />
</Graph> </Graph>
); );
const container = mount(graphWithTooltip); const container = mount(graphWithTooltip);
@ -145,7 +145,7 @@ describe('Graph', () => {
// Given // Given
const graphWithTooltip = ( const graphWithTooltip = (
<Graph {...mockGraphProps(true)}> <Graph {...mockGraphProps(true)}>
<Chart.Tooltip mode="multi" /> <VizTooltip mode={TooltipDisplayMode.Multi} />
</Graph> </Graph>
); );
const container = mount(graphWithTooltip); const container = mount(graphWithTooltip);

View File

@ -6,11 +6,12 @@ import uniqBy from 'lodash/uniqBy';
import { TimeRange, GraphSeriesXY, TimeZone, createDimension } from '@grafana/data'; import { TimeRange, GraphSeriesXY, TimeZone, createDimension } from '@grafana/data';
import _ from 'lodash'; import _ from 'lodash';
import { FlotPosition, FlotItem } from './types'; import { FlotPosition, FlotItem } from './types';
import { TooltipProps, TooltipContentProps, ActiveDimensions, Tooltip } from '../Chart/Tooltip'; import { VizTooltipProps, VizTooltipContentProps, ActiveDimensions, VizTooltip } from '../VizTooltip';
import { GraphTooltip } from './GraphTooltip/GraphTooltip'; import { GraphTooltip } from './GraphTooltip/GraphTooltip';
import { GraphContextMenu, GraphContextMenuProps, ContextDimensions } from './GraphContextMenu'; import { GraphContextMenu, GraphContextMenuProps, ContextDimensions } from './GraphContextMenu';
import { GraphDimensions } from './GraphTooltip/types'; import { GraphDimensions } from './GraphTooltip/types';
import { graphTimeFormat, graphTickFormatter } from './utils'; import { graphTimeFormat, graphTickFormatter } from './utils';
import { TooltipDisplayMode } from '../VizTooltip/models.gen';
export interface GraphProps { export interface GraphProps {
ariaLabel?: string; ariaLabel?: string;
@ -124,7 +125,7 @@ export class Graph extends PureComponent<GraphProps, GraphState> {
renderTooltip = () => { renderTooltip = () => {
const { children, series, timeZone } = this.props; const { children, series, timeZone } = this.props;
const { pos, activeItem, isTooltipVisible } = this.state; const { pos, activeItem, isTooltipVisible } = this.state;
let tooltipElement: React.ReactElement<TooltipProps> | null = null; let tooltipElement: React.ReactElement<VizTooltipProps> | null = null;
if (!isTooltipVisible || !pos || series.length === 0) { if (!isTooltipVisible || !pos || series.length === 0) {
return null; return null;
@ -139,15 +140,15 @@ export class Graph extends PureComponent<GraphProps, GraphState> {
// @ts-ignore // @ts-ignore
const childType = c && c.type && (c.type.displayName || c.type.name); const childType = c && c.type && (c.type.displayName || c.type.name);
if (childType === Tooltip.displayName) { if (childType === VizTooltip.displayName) {
tooltipElement = c as React.ReactElement<TooltipProps>; tooltipElement = c as React.ReactElement<VizTooltipProps>;
} }
}); });
// If no tooltip provided, skip rendering // If no tooltip provided, skip rendering
if (!tooltipElement) { if (!tooltipElement) {
return null; return null;
} }
const tooltipElementProps = (tooltipElement as React.ReactElement<TooltipProps>).props; const tooltipElementProps = (tooltipElement as React.ReactElement<VizTooltipProps>).props;
const tooltipMode = tooltipElementProps.mode || 'single'; const tooltipMode = tooltipElementProps.mode || 'single';
@ -172,7 +173,7 @@ export class Graph extends PureComponent<GraphProps, GraphState> {
yAxis: activeItem ? [activeItem.series.seriesIndex, activeItem.dataIndex] : null, yAxis: activeItem ? [activeItem.series.seriesIndex, activeItem.dataIndex] : null,
}; };
const tooltipContentProps: TooltipContentProps<GraphDimensions> = { const tooltipContentProps: VizTooltipContentProps<GraphDimensions> = {
dimensions: { dimensions: {
// time/value dimension columns are index-aligned - see getGraphSeriesModel // time/value dimension columns are index-aligned - see getGraphSeriesModel
xAxis: createDimension( xAxis: createDimension(
@ -186,13 +187,13 @@ export class Graph extends PureComponent<GraphProps, GraphState> {
}, },
activeDimensions, activeDimensions,
pos, pos,
mode: tooltipElementProps.mode || 'single', mode: tooltipElementProps.mode || TooltipDisplayMode.Single,
timeZone, timeZone,
}; };
const tooltipContent = React.createElement(tooltipContentRenderer, { ...tooltipContentProps }); const tooltipContent = React.createElement(tooltipContentRenderer, { ...tooltipContentProps });
return React.cloneElement<TooltipProps>(tooltipElement as React.ReactElement<TooltipProps>, { return React.cloneElement<VizTooltipProps>(tooltipElement as React.ReactElement<VizTooltipProps>, {
content: tooltipContent, content: tooltipContent,
position: { x: pos.pageX, y: pos.pageY }, position: { x: pos.pageX, y: pos.pageY },
offset: { x: 10, y: 10 }, offset: { x: 10, y: 10 },

View File

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import { TooltipContentProps } from '../../Chart/Tooltip'; import { VizTooltipContentProps } from '../../VizTooltip';
import { SingleModeGraphTooltip } from './SingleModeGraphTooltip'; import { SingleModeGraphTooltip } from './SingleModeGraphTooltip';
import { MultiModeGraphTooltip } from './MultiModeGraphTooltip'; import { MultiModeGraphTooltip } from './MultiModeGraphTooltip';
import { GraphDimensions } from './types'; import { GraphDimensions } from './types';
export const GraphTooltip: React.FC<TooltipContentProps<GraphDimensions>> = ({ export const GraphTooltip: React.FC<VizTooltipContentProps<GraphDimensions>> = ({
mode = 'single', mode = 'single',
dimensions, dimensions,
activeDimensions, activeDimensions,

View File

@ -3,7 +3,7 @@ import { mount } from 'enzyme';
import { MultiModeGraphTooltip } from './MultiModeGraphTooltip'; import { MultiModeGraphTooltip } from './MultiModeGraphTooltip';
import { createDimension, ArrayVector, FieldType } from '@grafana/data'; import { createDimension, ArrayVector, FieldType } from '@grafana/data';
import { GraphDimensions } from './types'; import { GraphDimensions } from './types';
import { ActiveDimensions } from '../../Chart/Tooltip'; import { ActiveDimensions } from '../../VizTooltip';
let dimensions: GraphDimensions; let dimensions: GraphDimensions;

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { SeriesTable } from './SeriesTable'; import { SeriesTable } from '../../VizTooltip';
import { GraphTooltipContentProps } from './types'; import { GraphTooltipContentProps } from './types';
import { getMultiSeriesGraphHoverInfo } from '../utils'; import { getMultiSeriesGraphHoverInfo } from '../utils';
import { FlotPosition } from '../types'; import { FlotPosition } from '../types';

View File

@ -6,7 +6,7 @@ import {
getDisplayProcessor, getDisplayProcessor,
getFieldDisplayName, getFieldDisplayName,
} from '@grafana/data'; } from '@grafana/data';
import { SeriesTable } from './SeriesTable'; import { SeriesTable } from '../../VizTooltip';
import { GraphTooltipContentProps } from './types'; import { GraphTooltipContentProps } from './types';
export const SingleModeGraphTooltip: React.FC<GraphTooltipContentProps> = ({ export const SingleModeGraphTooltip: React.FC<GraphTooltipContentProps> = ({

View File

@ -1,11 +0,0 @@
package grafanaschema
// TODO Relative imports are flatly disallowed by CUE, but that's what's
// currently done in the corresponding typescript code. We'll have to make
// cuetsy handle this with import mappings.
import tooltip "github.com/grafana/grafana/packages/grafana-ui/src/components/Chart:grafanaschema"
GraphTooltipOptions: {
mode: tooltip.TooltipMode
} @cuetsy(targetType="interface")

View File

@ -1,10 +1,5 @@
import { ActiveDimensions } from '../../Chart/Tooltip'; import { ActiveDimensions } from '../../VizTooltip';
import { Dimension, Dimensions, TimeZone } from '@grafana/data'; import { Dimension, Dimensions, TimeZone } from '@grafana/data';
import { TooltipMode } from '../../Chart/models.gen';
export interface GraphTooltipOptions {
mode: TooltipMode;
}
export interface GraphDimensions extends Dimensions { export interface GraphDimensions extends Dimensions {
xAxis: Dimension<number>; xAxis: Dimension<number>;

View File

@ -2,11 +2,11 @@ import React from 'react';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { InfoTooltip } from './InfoTooltip'; import { InfoTooltip } from './InfoTooltip';
import { Tooltip } from '../Chart/Tooltip'; import { VizTooltip } from '../VizTooltip';
export default { export default {
title: 'Overlays/TooltipInternal', title: 'Overlays/TooltipInternal',
component: Tooltip, component: VizTooltip,
decorators: [withCenteredStory], decorators: [withCenteredStory],
}; };

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { select, number, boolean } from '@storybook/addon-knobs'; import { select, number, boolean } from '@storybook/addon-knobs';
import { PieChart, PieChartType } from '@grafana/ui'; import { PieChart, PieChartType, TooltipDisplayMode } from '@grafana/ui';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { import {
FieldColorModeId, FieldColorModeId,
@ -52,15 +52,16 @@ const getKnobs = () => {
return { return {
width: number('Width', 500), width: number('Width', 500),
height: number('Height', 500), height: number('Height', 500),
pieType: select('pieType', [PieChartType.Pie, PieChartType.Donut], PieChartType.Pie), pieType: select('pieType', Object.values(PieChartType), PieChartType.Pie),
showLabelName: boolean('Label.showName', true), showLabelName: boolean('Label.showName', true),
showLabelValue: boolean('Label.showValue', false), showLabelValue: boolean('Label.showValue', false),
showLabelPercent: boolean('Label.showPercent', false), showLabelPercent: boolean('Label.showPercent', false),
tooltipMode: select('Tooltip mode', Object.values(TooltipDisplayMode), TooltipDisplayMode.Single),
}; };
}; };
export const basic = () => { export const basic = () => {
const { pieType, width, height } = getKnobs(); const { pieType, width, height, tooltipMode } = getKnobs();
return ( return (
<PieChart <PieChart
@ -71,12 +72,13 @@ export const basic = () => {
fieldConfig={fieldConfig} fieldConfig={fieldConfig}
data={datapoints} data={datapoints}
pieType={pieType} pieType={pieType}
tooltipOptions={{ mode: tooltipMode }}
/> />
); );
}; };
export const donut = () => { export const donut = () => {
const { width, height } = getKnobs(); const { width, height, tooltipMode } = getKnobs();
return ( return (
<PieChart <PieChart
@ -87,6 +89,7 @@ export const donut = () => {
fieldConfig={fieldConfig} fieldConfig={fieldConfig}
data={datapoints} data={datapoints}
pieType={PieChartType.Donut} pieType={PieChartType.Donut}
tooltipOptions={{ mode: tooltipMode }}
/> />
); );
}; };

View File

@ -1,6 +1,5 @@
import React, { FC, ReactNode } from 'react'; import React, { FC, ReactNode } from 'react';
import { import {
DisplayValue,
FALLBACK_COLOR, FALLBACK_COLOR,
FieldDisplay, FieldDisplay,
formattedValueToString, formattedValueToString,
@ -29,6 +28,8 @@ import {
PieChartSvgProps, PieChartSvgProps,
PieChartType, PieChartType,
} from './types'; } from './types';
import { getTooltipContainerStyles } from '../../themes/mixins';
import { SeriesTable, SeriesTableRowProps, VizTooltipOptions } from '../VizTooltip';
const defaultLegendOptions: PieChartLegendOptions = { const defaultLegendOptions: PieChartLegendOptions = {
displayMode: LegendDisplayMode.List, displayMode: LegendDisplayMode.List,
@ -44,6 +45,7 @@ export const PieChart: FC<PieChartProps> = ({
fieldConfig, fieldConfig,
replaceVariables, replaceVariables,
legendOptions = defaultLegendOptions, legendOptions = defaultLegendOptions,
tooltipOptions,
onSeriesColorChange, onSeriesColorChange,
width, width,
height, height,
@ -112,7 +114,13 @@ export const PieChart: FC<PieChartProps> = ({
<VizLayout width={width} height={height} legend={getLegend(fieldDisplayValues, legendOptions)}> <VizLayout width={width} height={height} legend={getLegend(fieldDisplayValues, legendOptions)}>
{(vizWidth: number, vizHeight: number) => { {(vizWidth: number, vizHeight: number) => {
return ( return (
<PieChartSvg width={vizWidth} height={vizHeight} fieldDisplayValues={fieldDisplayValues} {...restProps} /> <PieChartSvg
width={vizWidth}
height={vizHeight}
fieldDisplayValues={fieldDisplayValues}
tooltipOptions={tooltipOptions}
{...restProps}
/>
); );
}} }}
</VizLayout> </VizLayout>
@ -126,11 +134,12 @@ export const PieChartSvg: FC<PieChartSvgProps> = ({
height, height,
useGradients = true, useGradients = true,
displayLabels = [], displayLabels = [],
tooltipOptions,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const componentInstanceId = useComponentInstanceId('PieChart'); const componentInstanceId = useComponentInstanceId('PieChart');
const styles = useStyles(getStyles); const styles = useStyles(getStyles);
const tooltip = useTooltip<DisplayValue>(); const tooltip = useTooltip<SeriesTableRowProps[]>();
const { containerRef, TooltipInPortal } = useTooltipInPortal({ const { containerRef, TooltipInPortal } = useTooltipInPortal({
detectBounds: true, detectBounds: true,
scroll: true, scroll: true,
@ -147,6 +156,7 @@ export const PieChartSvg: FC<PieChartSvgProps> = ({
}; };
const showLabel = displayLabels.length > 0; const showLabel = displayLabels.length > 0;
const showTooltip = tooltipOptions.mode !== 'none' && tooltip.tooltipOpen;
const total = fieldDisplayValues.reduce((acc, item) => item.display.numeric + acc, 0); const total = fieldDisplayValues.reduce((acc, item) => item.display.numeric + acc, 0);
const layout = getPieLayout(width, height, pieType); const layout = getPieLayout(width, height, pieType);
const colors = [ const colors = [
@ -204,6 +214,7 @@ export const PieChartSvg: FC<PieChartSvgProps> = ({
pie={pie} pie={pie}
fill={getGradientColor(color)} fill={getGradientColor(color)}
openMenu={api.openMenu} openMenu={api.openMenu}
tooltipOptions={tooltipOptions}
> >
{label} {label}
</PieSlice> </PieSlice>
@ -212,7 +223,14 @@ export const PieChartSvg: FC<PieChartSvgProps> = ({
); );
} else { } else {
return ( return (
<PieSlice key={arc.index} tooltip={tooltip} arc={arc} pie={pie} fill={getGradientColor(color)}> <PieSlice
key={arc.index}
tooltip={tooltip}
arc={arc}
pie={pie}
fill={getGradientColor(color)}
tooltipOptions={tooltipOptions}
>
{label} {label}
</PieSlice> </PieSlice>
); );
@ -222,16 +240,18 @@ export const PieChartSvg: FC<PieChartSvgProps> = ({
</Pie> </Pie>
</Group> </Group>
</svg> </svg>
{tooltip.tooltipOpen && ( {showTooltip ? (
<TooltipInPortal <TooltipInPortal
key={Math.random()} key={Math.random()}
top={tooltip.tooltipTop} top={tooltip.tooltipTop}
className={styles.tooltipPortal} className={styles.tooltipPortal}
left={tooltip.tooltipLeft} left={tooltip.tooltipLeft}
unstyled={true}
applyPositionStyle={true}
> >
{tooltip.tooltipData!.title} {formattedValueToString(tooltip.tooltipData!)} <SeriesTable series={tooltip.tooltipData!} />
</TooltipInPortal> </TooltipInPortal>
)} ) : null}
</div> </div>
); );
}; };
@ -241,18 +261,19 @@ const PieSlice: FC<{
arc: PieArcDatum<FieldDisplay>; arc: PieArcDatum<FieldDisplay>;
pie: ProvidedProps<FieldDisplay>; pie: ProvidedProps<FieldDisplay>;
fill: string; fill: string;
tooltip: UseTooltipParams<DisplayValue>; tooltip: UseTooltipParams<SeriesTableRowProps[]>;
tooltipOptions: VizTooltipOptions;
openMenu?: (event: React.MouseEvent<SVGElement>) => void; openMenu?: (event: React.MouseEvent<SVGElement>) => void;
}> = ({ arc, children, pie, openMenu, fill, tooltip }) => { }> = ({ arc, children, pie, openMenu, fill, tooltip, tooltipOptions }) => {
const theme = useTheme(); const theme = useTheme();
const styles = useStyles(getStyles); const styles = useStyles(getStyles);
const onMouseMoveOverArc = (event: any, datum: any) => { const onMouseMoveOverArc = (event: any) => {
const coords = localPoint(event.target.ownerSVGElement, event); const coords = localPoint(event.target.ownerSVGElement, event);
tooltip.showTooltip({ tooltip.showTooltip({
tooltipLeft: coords!.x, tooltipLeft: coords!.x,
tooltipTop: coords!.y, tooltipTop: coords!.y,
tooltipData: datum, tooltipData: getTooltipData(pie, arc, tooltipOptions),
}); });
}; };
@ -260,7 +281,7 @@ const PieSlice: FC<{
<g <g
key={arc.data.display.title} key={arc.data.display.title}
className={styles.svgArg} className={styles.svgArg}
onMouseMove={(event) => onMouseMoveOverArc(event, arc.data.display)} onMouseMove={tooltipOptions.mode !== 'none' ? onMouseMoveOverArc : undefined}
onMouseOut={tooltip.hideTooltip} onMouseOut={tooltip.hideTooltip}
onClick={openMenu} onClick={openMenu}
> >
@ -321,6 +342,30 @@ const PieLabel: FC<{
); );
}; };
function getTooltipData(
pie: ProvidedProps<FieldDisplay>,
arc: PieArcDatum<FieldDisplay>,
tooltipOptions: VizTooltipOptions
) {
if (tooltipOptions.mode === 'multi') {
return pie.arcs.map((pieArc) => {
return {
color: pieArc.data.display.color ?? FALLBACK_COLOR,
label: pieArc.data.display.title,
value: formattedValueToString(pieArc.data.display),
isActive: pieArc.index === arc.index,
};
});
}
return [
{
color: arc.data.display.color ?? FALLBACK_COLOR,
label: arc.data.display.title,
value: formattedValueToString(arc.data.display),
},
];
}
function getLabelPos(arc: PieArcDatum<FieldDisplay>, outerRadius: number, innerRadius: number) { function getLabelPos(arc: PieArcDatum<FieldDisplay>, outerRadius: number, innerRadius: number) {
const r = (outerRadius + innerRadius) / 2; const r = (outerRadius + innerRadius) / 2;
const a = (+arc.startAngle + +arc.endAngle) / 2 - Math.PI / 2; const a = (+arc.startAngle + +arc.endAngle) / 2 - Math.PI / 2;
@ -382,7 +427,7 @@ const getStyles = (theme: GrafanaTheme) => {
} }
`, `,
tooltipPortal: css` tooltipPortal: css`
z-index: 1050; ${getTooltipContainerStyles(theme)}
`, `,
}; };
}; };

View File

@ -1,4 +1,5 @@
import { DataFrame, FieldConfigSource, FieldDisplay, InterpolateFunction, ReduceDataOptions } from '@grafana/data'; import { DataFrame, FieldConfigSource, FieldDisplay, InterpolateFunction, ReduceDataOptions } from '@grafana/data';
import { VizTooltipOptions } from '../VizTooltip';
import { VizLegendOptions } from '..'; import { VizLegendOptions } from '..';
export interface PieChartSvgProps { export interface PieChartSvgProps {
@ -9,10 +10,12 @@ export interface PieChartSvgProps {
displayLabels?: PieChartLabels[]; displayLabels?: PieChartLabels[];
useGradients?: boolean; useGradients?: boolean;
onSeriesColorChange?: (label: string, color: string) => void; onSeriesColorChange?: (label: string, color: string) => void;
tooltipOptions: VizTooltipOptions;
} }
export interface PieChartProps extends Omit<PieChartSvgProps, 'fieldDisplayValues'> { export interface PieChartProps extends Omit<PieChartSvgProps, 'fieldDisplayValues'> {
legendOptions?: PieChartLegendOptions; legendOptions?: PieChartLegendOptions;
tooltipOptions: VizTooltipOptions;
reduceOptions: ReduceDataOptions; reduceOptions: ReduceDataOptions;
fieldConfig: FieldConfigSource<any>; fieldConfig: FieldConfigSource<any>;
replaceVariables: InterpolateFunction; replaceVariables: InterpolateFunction;

View File

@ -0,0 +1,61 @@
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { SeriesTable, SeriesTableProps } from './SeriesTable';
export default {
title: 'VizTooltip/SeriesTable',
component: SeriesTable,
argTypes: {
timestamp: {
control: 'date',
},
},
} as Meta;
const Template: Story<SeriesTableProps> = (args) => {
const date = new Date(args.timestamp!).toLocaleString();
return (
<div>
<SeriesTable {...args} timestamp={date} />
</div>
);
};
export const basic = Template.bind({});
basic.args = {
timestamp: new Date('2021-01-01T00:00:00').toISOString(),
series: [
{
color: '#299c46',
label: 'label 1',
value: '100 W',
},
],
};
export const multi = Template.bind({});
multi.args = {
timestamp: new Date('2021-01-01T00:00:00').toISOString(),
series: [
{
color: '#299c46',
label: 'label 1',
value: '100 W',
isActive: false,
},
{
color: '#9933cc',
label: 'label yes',
value: '25 W',
isActive: true,
},
{
color: '#eb7b18',
label: 'label 3',
value: '150 W',
isActive: false,
},
],
};

View File

@ -1,10 +1,12 @@
import React from 'react'; import React from 'react';
import { stylesFactory } from '../../../themes/stylesFactory';
import { GrafanaTheme, GraphSeriesValue } from '@grafana/data'; import { GrafanaTheme, GraphSeriesValue } from '@grafana/data';
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import { SeriesIcon } from '../../VizLegend/SeriesIcon'; import { SeriesIcon } from '../VizLegend/SeriesIcon';
import { useTheme } from '../../../themes'; import { useStyles } from '../../themes';
/**
* @public
*/
export interface SeriesTableRowProps { export interface SeriesTableRowProps {
color?: string; color?: string;
label?: string; label?: string;
@ -12,10 +14,11 @@ export interface SeriesTableRowProps {
isActive?: boolean; isActive?: boolean;
} }
const getSeriesTableRowStyles = stylesFactory((theme: GrafanaTheme) => { const getSeriesTableRowStyles = (theme: GrafanaTheme) => {
return { return {
icon: css` icon: css`
margin-right: ${theme.spacing.xs}; margin-right: ${theme.spacing.xs};
vertical-align: middle;
`, `,
seriesTable: css` seriesTable: css`
display: table; display: table;
@ -41,11 +44,11 @@ const getSeriesTableRowStyles = stylesFactory((theme: GrafanaTheme) => {
font-size: ${theme.typography.size.sm}; font-size: ${theme.typography.size.sm};
`, `,
}; };
}); };
const SeriesTableRow: React.FC<SeriesTableRowProps> = ({ color, label, value, isActive }) => { const SeriesTableRow: React.FC<SeriesTableRowProps> = ({ color, label, value, isActive }) => {
const theme = useTheme(); const styles = useStyles(getSeriesTableRowStyles);
const styles = getSeriesTableRowStyles(theme);
return ( return (
<div className={cx(styles.seriesTableRow, isActive && styles.activeSeries)}> <div className={cx(styles.seriesTableRow, isActive && styles.activeSeries)}>
{color && ( {color && (
@ -59,14 +62,19 @@ const SeriesTableRow: React.FC<SeriesTableRowProps> = ({ color, label, value, is
); );
}; };
interface SeriesTableProps { /**
* @public
*/
export interface SeriesTableProps {
timestamp?: string | GraphSeriesValue; timestamp?: string | GraphSeriesValue;
series: SeriesTableRowProps[]; series: SeriesTableRowProps[];
} }
/**
* @public
*/
export const SeriesTable: React.FC<SeriesTableProps> = ({ timestamp, series }) => { export const SeriesTable: React.FC<SeriesTableProps> = ({ timestamp, series }) => {
const theme = useTheme(); const styles = useStyles(getSeriesTableRowStyles);
const styles = getSeriesTableRowStyles(theme);
return ( return (
<> <>

View File

@ -3,9 +3,9 @@ import { css } from '@emotion/css';
import { Portal } from '../Portal/Portal'; import { Portal } from '../Portal/Portal';
import { Dimensions, TimeZone } from '@grafana/data'; import { Dimensions, TimeZone } from '@grafana/data';
import { FlotPosition } from '../Graph/types'; import { FlotPosition } from '../Graph/types';
import { TooltipContainer } from './TooltipContainer'; import { VizTooltipContainer } from './VizTooltipContainer';
import { useStyles } from '../../themes'; import { useStyles } from '../../themes';
import { TooltipMode } from './models.gen'; import { TooltipDisplayMode } from './models.gen';
// Describes active dimensions user interacts with // Describes active dimensions user interacts with
// It's a key-value pair where: // It's a key-value pair where:
@ -14,7 +14,7 @@ import { TooltipMode } from './models.gen';
// If row is undefined, it means that we are not hovering over a datapoint // If row is undefined, it means that we are not hovering over a datapoint
export type ActiveDimensions<T extends Dimensions = any> = { [key in keyof T]: [number, number | undefined] | null }; export type ActiveDimensions<T extends Dimensions = any> = { [key in keyof T]: [number, number | undefined] | null };
export interface TooltipContentProps<T extends Dimensions = any> { export interface VizTooltipContentProps<T extends Dimensions = any> {
// Each dimension is described by array of fields representing it // Each dimension is described by array of fields representing it
// I.e. for graph there are two dimensions: x and y axis: // I.e. for graph there are two dimensions: x and y axis:
// { xAxis: [<array of time fields>], yAxis: [<array of value fields>]} // { xAxis: [<array of time fields>], yAxis: [<array of value fields>]}
@ -23,15 +23,15 @@ export interface TooltipContentProps<T extends Dimensions = any> {
activeDimensions?: ActiveDimensions<T>; activeDimensions?: ActiveDimensions<T>;
timeZone?: TimeZone; timeZone?: TimeZone;
pos: FlotPosition; pos: FlotPosition;
mode: TooltipMode; mode: TooltipDisplayMode;
} }
export interface TooltipProps { export interface VizTooltipProps {
/** Element used as tooltips content */ /** Element used as tooltips content */
content?: React.ReactElement<any>; content?: React.ReactElement<any>;
/** Optional component to be used as a tooltip content */ /** Optional component to be used as a tooltip content */
tooltipComponent?: React.ComponentType<TooltipContentProps>; tooltipComponent?: React.ComponentType<VizTooltipContentProps>;
/** x/y position relative to the window */ /** x/y position relative to the window */
position?: { x: number; y: number }; position?: { x: number; y: number };
@ -42,24 +42,27 @@ export interface TooltipProps {
// Mode in which tooltip works // Mode in which tooltip works
// - single - display single series info // - single - display single series info
// - multi - display all series info // - multi - display all series info
mode?: TooltipMode; mode?: TooltipDisplayMode;
} }
export const Tooltip: React.FC<TooltipProps> = ({ content, position, offset }) => { /**
* @public
*/
export const VizTooltip: React.FC<VizTooltipProps> = ({ content, position, offset }) => {
const styles = useStyles(getStyles); const styles = useStyles(getStyles);
if (position) { if (position) {
return ( return (
<Portal className={styles.portal}> <Portal className={styles.portal}>
<TooltipContainer position={position} offset={offset || { x: 0, y: 0 }}> <VizTooltipContainer position={position} offset={offset || { x: 0, y: 0 }}>
{content} {content}
</TooltipContainer> </VizTooltipContainer>
</Portal> </Portal>
); );
} }
return null; return null;
}; };
Tooltip.displayName = 'ChartTooltip'; VizTooltip.displayName = 'VizTooltip';
const getStyles = () => { const getStyles = () => {
return { return {

View File

@ -1,24 +1,29 @@
import React, { useState, useLayoutEffect, useRef, HTMLAttributes, useMemo } from 'react'; import React, { useState, useLayoutEffect, useRef, HTMLAttributes, useMemo } from 'react';
import { stylesFactory } from '../../themes/stylesFactory';
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import { useTheme } from '../../themes/ThemeContext'; import { useStyles } from '../../themes';
import { getTooltipContainerStyles } from '../../themes/mixins';
import useWindowSize from 'react-use/lib/useWindowSize'; import useWindowSize from 'react-use/lib/useWindowSize';
import { Dimensions2D, GrafanaTheme } from '@grafana/data'; import { Dimensions2D, GrafanaTheme } from '@grafana/data';
interface TooltipContainerProps extends HTMLAttributes<HTMLDivElement> { /**
* @public
*/
export interface VizTooltipContainerProps extends HTMLAttributes<HTMLDivElement> {
position: { x: number; y: number }; position: { x: number; y: number };
offset: { x: number; y: number }; offset: { x: number; y: number };
children?: JSX.Element; children?: JSX.Element;
} }
export const TooltipContainer: React.FC<TooltipContainerProps> = ({ /**
* @public
*/
export const VizTooltipContainer: React.FC<VizTooltipContainerProps> = ({
position: { x: positionX, y: positionY }, position: { x: positionX, y: positionY },
offset: { x: offsetX, y: offsetY }, offset: { x: offsetX, y: offsetY },
children, children,
className, className,
...otherProps ...otherProps
}) => { }) => {
const theme = useTheme();
const tooltipRef = useRef<HTMLDivElement>(null); const tooltipRef = useRef<HTMLDivElement>(null);
const tooltipMeasurementRef = useRef<Dimensions2D>({ width: 0, height: 0 }); const tooltipMeasurementRef = useRef<Dimensions2D>({ width: 0, height: 0 });
const { width, height } = useWindowSize(); const { width, height } = useWindowSize();
@ -80,7 +85,7 @@ export const TooltipContainer: React.FC<TooltipContainerProps> = ({
}); });
}, [width, height, positionX, offsetX, positionY, offsetY]); }, [width, height, positionX, offsetX, positionY, offsetY]);
const styles = getTooltipContainerStyles(theme); const styles = useStyles(getStyles);
return ( return (
<div <div
@ -99,18 +104,10 @@ export const TooltipContainer: React.FC<TooltipContainerProps> = ({
); );
}; };
TooltipContainer.displayName = 'TooltipContainer'; VizTooltipContainer.displayName = 'VizTooltipContainer';
const getTooltipContainerStyles = stylesFactory((theme: GrafanaTheme) => { const getStyles = (theme: GrafanaTheme) => ({
return { wrapper: css`
wrapper: css` ${getTooltipContainerStyles(theme)}
overflow: hidden; `,
background: ${theme.colors.bg2};
/* max-width is set up based on .grafana-tooltip class that's used in dashboard */
max-width: 800px;
padding: ${theme.spacing.sm};
border-radius: ${theme.border.radius.sm};
z-index: ${theme.zIndex.tooltip};
`,
};
}); });

View File

@ -0,0 +1,4 @@
export { VizTooltip, VizTooltipContentProps, VizTooltipProps, ActiveDimensions } from './VizTooltip';
export { VizTooltipContainer, VizTooltipContainerProps } from './VizTooltipContainer';
export { SeriesTable, SeriesTableProps, SeriesTableRowProps } from './SeriesTable';
export { TooltipDisplayMode, VizTooltipOptions } from './models.gen';

View File

@ -0,0 +1,7 @@
package grafanaschema
TooltipDisplayMode: "single" | "multi" | "none" @cuetsy(targetType="enum")
VizTooltipOptions: {
mode: TooltipDisplayMode
} @cuetsy(targetType="interface")

View File

@ -3,8 +3,18 @@
// It is currenty hand written but will serve as the target for cuetsy // It is currenty hand written but will serve as the target for cuetsy
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import { TooltipMode } from '../../Chart/models.gen'; /**
* @public
export interface GraphTooltipOptions { */
mode: TooltipMode; export enum TooltipDisplayMode {
Single = 'single',
Multi = 'multi',
None = 'none',
} }
/**
* @public
*/
export type VizTooltipOptions = {
mode: TooltipDisplayMode;
};

View File

@ -74,7 +74,15 @@ export { Graph } from './Graph/Graph';
export { GraphWithLegend } from './Graph/GraphWithLegend'; export { GraphWithLegend } from './Graph/GraphWithLegend';
export { GraphContextMenu, GraphContextMenuHeader } from './Graph/GraphContextMenu'; export { GraphContextMenu, GraphContextMenuHeader } from './Graph/GraphContextMenu';
export { BarGauge, BarGaugeDisplayMode } from './BarGauge/BarGauge'; export { BarGauge, BarGaugeDisplayMode } from './BarGauge/BarGauge';
export { GraphTooltipOptions } from './Graph/GraphTooltip/types'; export {
VizTooltip,
VizTooltipContainer,
SeriesTable,
VizTooltipOptions,
TooltipDisplayMode,
SeriesTableProps,
SeriesTableRowProps,
} from './VizTooltip';
export { VizRepeater, VizRepeaterRenderValueProps } from './VizRepeater/VizRepeater'; export { VizRepeater, VizRepeaterRenderValueProps } from './VizRepeater/VizRepeater';
export { graphTimeFormat, graphTickFormatter } from './Graph/utils'; export { graphTimeFormat, graphTickFormatter } from './Graph/utils';
export { export {
@ -132,8 +140,6 @@ export { Spinner } from './Spinner/Spinner';
export { FadeTransition } from './transitions/FadeTransition'; export { FadeTransition } from './transitions/FadeTransition';
export { SlideOutTransition } from './transitions/SlideOutTransition'; export { SlideOutTransition } from './transitions/SlideOutTransition';
export { Segment, SegmentAsync, SegmentInput, SegmentSelect } from './Segment/'; export { Segment, SegmentAsync, SegmentInput, SegmentSelect } from './Segment/';
export { default as Chart } from './Chart';
export { TooltipContainer } from './Chart/TooltipContainer';
export { Drawer } from './Drawer/Drawer'; export { Drawer } from './Drawer/Drawer';
export { Slider } from './Slider/Slider'; export { Slider } from './Slider/Slider';
export { RangeSlider } from './Slider/RangeSlider'; export { RangeSlider } from './Slider/RangeSlider';

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Portal } from '../../Portal/Portal'; import { Portal } from '../../Portal/Portal';
import { usePlotContext } from '../context'; import { usePlotContext } from '../context';
import { CursorPlugin } from './CursorPlugin'; import { CursorPlugin } from './CursorPlugin';
import { SeriesTable, SeriesTableRowProps } from '../../Graph/GraphTooltip/SeriesTable'; import { VizTooltipContainer, SeriesTable, SeriesTableRowProps, TooltipDisplayMode } from '../../VizTooltip';
import { import {
DataFrame, DataFrame,
FieldType, FieldType,
@ -11,12 +11,10 @@ import {
getFieldDisplayName, getFieldDisplayName,
TimeZone, TimeZone,
} from '@grafana/data'; } from '@grafana/data';
import { TooltipContainer } from '../../Chart/TooltipContainer';
import { TooltipMode } from '../../Chart/models.gen';
import { useGraphNGContext } from '../../GraphNG/hooks'; import { useGraphNGContext } from '../../GraphNG/hooks';
interface TooltipPluginProps { interface TooltipPluginProps {
mode?: TooltipMode; mode?: TooltipDisplayMode;
timeZone: TimeZone; timeZone: TimeZone;
data: DataFrame[]; data: DataFrame[];
} }
@ -121,9 +119,9 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({ mode = 'single', t
return ( return (
<Portal> <Portal>
<TooltipContainer position={{ x: coords.viewport.x, y: coords.viewport.y }} offset={{ x: 10, y: 10 }}> <VizTooltipContainer position={{ x: coords.viewport.x, y: coords.viewport.y }} offset={{ x: 10, y: 10 }}>
{tooltip} {tooltip}
</TooltipContainer> </VizTooltipContainer>
</Portal> </Portal>
); );
}} }}

View File

@ -4,13 +4,13 @@ import tinycolor from 'tinycolor2';
export function cardChrome(theme: GrafanaTheme): string { export function cardChrome(theme: GrafanaTheme): string {
return ` return `
background: ${theme.colors.bg2}; background: ${theme.colors.bg2};
&:hover { &:hover {
background: ${hoverColor(theme.colors.bg2, theme)}; background: ${hoverColor(theme.colors.bg2, theme)};
} }
box-shadow: ${theme.shadows.listItem}; box-shadow: ${theme.shadows.listItem};
border-radius: ${theme.border.radius.md}; border-radius: ${theme.border.radius.md};
`; `;
} }
export function hoverColor(color: string, theme: GrafanaTheme): string { export function hoverColor(color: string, theme: GrafanaTheme): string {
@ -30,9 +30,9 @@ export function listItem(theme: GrafanaTheme): string {
export function listItemSelected(theme: GrafanaTheme): string { export function listItemSelected(theme: GrafanaTheme): string {
return ` return `
background: ${hoverColor(theme.colors.bg2, theme)}; background: ${hoverColor(theme.colors.bg2, theme)};
color: ${theme.colors.textStrong}; color: ${theme.colors.textStrong};
`; `;
} }
export function mediaUp(breakpoint: string) { export function mediaUp(breakpoint: string) {
@ -61,3 +61,13 @@ export function getFocusStyles(theme: GrafanaThemeV2): CSSObject {
transition: `all 0.2s cubic-bezier(0.19, 1, 0.22, 1)`, transition: `all 0.2s cubic-bezier(0.19, 1, 0.22, 1)`,
}; };
} }
// max-width is set up based on .grafana-tooltip class that's used in dashboard
export const getTooltipContainerStyles = (theme: GrafanaTheme) => `
overflow: hidden;
background: ${theme.colors.bg2};
max-width: 800px;
padding: ${theme.spacing.sm};
border-radius: ${theme.border.radius.sm};
z-index: ${theme.zIndex.tooltip};
`;

View File

@ -21,6 +21,7 @@ import {
useStyles, useStyles,
useTheme, useTheme,
ZoomPlugin, ZoomPlugin,
TooltipDisplayMode,
} from '@grafana/ui'; } from '@grafana/ui';
import { defaultGraphConfig, getGraphFieldConfig } from 'app/plugins/panel/timeseries/config'; import { defaultGraphConfig, getGraphFieldConfig } from 'app/plugins/panel/timeseries/config';
import { hideSeriesConfigFactory } from 'app/plugins/panel/timeseries/overrides/hideSeriesConfigFactory'; import { hideSeriesConfigFactory } from 'app/plugins/panel/timeseries/overrides/hideSeriesConfigFactory';
@ -129,7 +130,7 @@ export function ExploreGraphNGPanel({
timeZone={timeZone} timeZone={timeZone}
> >
<ZoomPlugin onZoom={onUpdateTimeRange} /> <ZoomPlugin onZoom={onUpdateTimeRange} />
<TooltipPlugin data={data} mode="single" timeZone={timeZone} /> <TooltipPlugin data={data} mode={TooltipDisplayMode.Single} timeZone={timeZone} />
<ContextMenuPlugin data={data} timeZone={timeZone} /> <ContextMenuPlugin data={data} timeZone={timeZone} />
{annotations && <ExemplarsPlugin exemplars={annotations} timeZone={timeZone} getFieldLinks={getFieldLinks} />} {annotations && <ExemplarsPlugin exemplars={annotations} timeZone={timeZone} getFieldLinks={getFieldLinks} />}
</GraphNG> </GraphNG>

View File

@ -11,8 +11,9 @@ import {
Collapse, Collapse,
GraphSeriesToggler, GraphSeriesToggler,
GraphSeriesTogglerAPI, GraphSeriesTogglerAPI,
Chart, VizTooltip,
Icon, Icon,
TooltipDisplayMode,
} from '@grafana/ui'; } from '@grafana/ui';
const MAX_NUMBER_OF_TIME_SERIES = 20; const MAX_NUMBER_OF_TIME_SERIES = 20;
@ -130,7 +131,7 @@ class UnThemedExploreGraphPanel extends PureComponent<Props, State> {
onHorizontalRegionSelected={this.onChangeTime} onHorizontalRegionSelected={this.onChangeTime}
> >
{/* For logs we are using mulit mode until we refactor logs histogram to use barWidth instead of lineWidth to render bars */} {/* For logs we are using mulit mode until we refactor logs histogram to use barWidth instead of lineWidth to render bars */}
<Chart.Tooltip mode={showBars ? 'multi' : 'single'} /> <VizTooltip mode={showBars ? TooltipDisplayMode.Multi : TooltipDisplayMode.Single} />
</GraphWithLegend> </GraphWithLegend>
); );
}} }}

View File

@ -1,4 +1,4 @@
import { GraphTooltipOptions, LegendDisplayMode, LegendPlacement } from '@grafana/ui'; import { VizTooltipOptions, TooltipDisplayMode, LegendDisplayMode, LegendPlacement } from '@grafana/ui';
import { YAxis } from '@grafana/data'; import { YAxis } from '@grafana/data';
export interface SeriesOptions { export interface SeriesOptions {
@ -21,7 +21,7 @@ export interface Options {
series: { series: {
[alias: string]: SeriesOptions; [alias: string]: SeriesOptions;
}; };
tooltipOptions: GraphTooltipOptions; tooltipOptions: VizTooltipOptions;
} }
export const defaults: Options = { export const defaults: Options = {
@ -35,7 +35,7 @@ export const defaults: Options = {
placement: 'bottom', placement: 'bottom',
}, },
series: {}, series: {},
tooltipOptions: { mode: 'single' }, tooltipOptions: { mode: TooltipDisplayMode.Single },
}; };
export interface GraphLegendEditorLegendOptions { export interface GraphLegendEditorLegendOptions {

View File

@ -36,6 +36,7 @@ export const PieChartPanel: React.FC<Props> = ({
pieType={options.pieType} pieType={options.pieType}
displayLabels={options.displayLabels} displayLabels={options.displayLabels}
legendOptions={options.legend} legendOptions={options.legend}
tooltipOptions={options.tooltip}
/> />
); );
}; };

View File

@ -49,6 +49,19 @@ export const plugin = new PanelPlugin<PieChartOptions>(PieChartPanel)
], ],
}, },
}) })
.addRadio({
name: 'Tooltip mode',
path: 'tooltip.mode',
description: '',
defaultValue: 'single',
settings: {
options: [
{ value: 'single', label: 'Single' },
{ value: 'multi', label: 'All' },
{ value: 'none', label: 'Hidden' },
],
},
})
.addRadio({ .addRadio({
path: 'legend.displayMode', path: 'legend.displayMode',
name: 'Legend mode', name: 'Legend mode',

View File

@ -1,7 +1,13 @@
import { PieChartType, SingleStatBaseOptions, PieChartLabels, PieChartLegendOptions } from '@grafana/ui'; import {
PieChartType,
SingleStatBaseOptions,
PieChartLabels,
PieChartLegendOptions,
VizTooltipOptions,
} from '@grafana/ui';
export interface PieChartOptions extends SingleStatBaseOptions { export interface PieChartOptions extends SingleStatBaseOptions {
pieType: PieChartType; pieType: PieChartType;
displayLabels: PieChartLabels[]; displayLabels: PieChartLabels[];
legend: PieChartLegendOptions; legend: PieChartLegendOptions;
tooltip: VizTooltipOptions;
} }

View File

@ -20,6 +20,7 @@ import {
LineStyle, LineStyle,
PointVisibility, PointVisibility,
StackingMode, StackingMode,
TooltipDisplayMode,
} from '@grafana/ui'; } from '@grafana/ui';
import { Options } from './types'; import { Options } from './types';
import omitBy from 'lodash/omitBy'; import omitBy from 'lodash/omitBy';
@ -291,7 +292,7 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour
calcs: [], calcs: [],
}, },
tooltipOptions: { tooltipOptions: {
mode: 'single', mode: TooltipDisplayMode.Single,
}, },
}; };

View File

@ -1,6 +1,6 @@
import React, { useCallback, useRef, useState } from 'react'; import React, { useCallback, useRef, useState } from 'react';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
import { HorizontalGroup, Portal, Tag, TooltipContainer, useStyles } from '@grafana/ui'; import { HorizontalGroup, Portal, Tag, VizTooltipContainer, useStyles } from '@grafana/ui';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
interface AnnotationMarkerProps { interface AnnotationMarkerProps {
@ -38,7 +38,7 @@ export const AnnotationMarker: React.FC<AnnotationMarkerProps> = ({ time, text,
const elBBox = el.getBoundingClientRect(); const elBBox = el.getBoundingClientRect();
return ( return (
<TooltipContainer <VizTooltipContainer
position={{ x: elBBox.left, y: elBBox.top + elBBox.height }} position={{ x: elBBox.left, y: elBBox.top + elBBox.height }}
offset={{ x: 0, y: 0 }} offset={{ x: 0, y: 0 }}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
@ -61,7 +61,7 @@ export const AnnotationMarker: React.FC<AnnotationMarkerProps> = ({ time, text,
</> </>
</div> </div>
</div> </div>
</TooltipContainer> </VizTooltipContainer>
); );
}, [onMouseEnter, onMouseLeave, styles, time, text, tags]); }, [onMouseEnter, onMouseLeave, styles, time, text, tags]);

View File

@ -8,7 +8,7 @@ import {
systemDateFormats, systemDateFormats,
TimeZone, TimeZone,
} from '@grafana/data'; } from '@grafana/data';
import { FieldLinkList, Portal, TooltipContainer, useStyles } from '@grafana/ui'; import { FieldLinkList, Portal, VizTooltipContainer, useStyles } from '@grafana/ui';
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import React, { useCallback, useRef, useState } from 'react'; import React, { useCallback, useRef, useState } from 'react';
@ -58,7 +58,7 @@ export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({ timeZone, dataFr
const elBBox = el.getBoundingClientRect(); const elBBox = el.getBoundingClientRect();
return ( return (
<TooltipContainer <VizTooltipContainer
position={{ x: elBBox.left, y: elBBox.top + elBBox.height }} position={{ x: elBBox.left, y: elBBox.top + elBBox.height }}
offset={{ x: 0, y: 0 }} offset={{ x: 0, y: 0 }}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
@ -93,7 +93,7 @@ export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({ timeZone, dataFr
</div> </div>
</div> </div>
</div> </div>
</TooltipContainer> </VizTooltipContainer>
); );
}, [dataFrame.fields, getFieldLinks, index, onMouseEnter, onMouseLeave, styles, timeFormatter]); }, [dataFrame.fields, getFieldLinks, index, onMouseEnter, onMouseLeave, styles, timeFormatter]);

View File

@ -1,9 +1,9 @@
import { VizLegendOptions, GraphTooltipOptions } from '@grafana/ui'; import { VizLegendOptions, VizTooltipOptions } from '@grafana/ui';
export interface OptionsWithLegend { export interface OptionsWithLegend {
legend: VizLegendOptions; legend: VizLegendOptions;
} }
export interface Options extends OptionsWithLegend { export interface Options extends OptionsWithLegend {
tooltipOptions: GraphTooltipOptions; tooltipOptions: VizTooltipOptions;
} }

View File

@ -1,4 +1,4 @@
import { GraphTooltipOptions } from '@grafana/ui'; import { VizTooltipOptions } from '@grafana/ui';
import { OptionsWithLegend } from '../timeseries/types'; import { OptionsWithLegend } from '../timeseries/types';
export interface XYDimensionConfig { export interface XYDimensionConfig {
@ -9,5 +9,5 @@ export interface XYDimensionConfig {
export interface Options extends OptionsWithLegend { export interface Options extends OptionsWithLegend {
dims: XYDimensionConfig; dims: XYDimensionConfig;
tooltipOptions: GraphTooltipOptions; tooltipOptions: VizTooltipOptions;
} }

View File

@ -46,13 +46,13 @@ cue def -s $(find packages/grafana-data -type f -name "*.cue") > cue/data/gen.cu
# #
# It's important to understand why this is necessary, though. We are expecting # It's important to understand why this is necessary, though. We are expecting
# that these core components may depend on each other - e.g., how # that these core components may depend on each other - e.g., how
# GraphTooltipOptions composes in TooltipMode. We have to preserve those # VizTooltipOptions composes in TooltipDisplayMode. We have to preserve those
# literal identifiers in our assembled CUE, so that when a panel plugin's # literal identifiers in our assembled CUE, so that when a panel plugin's
# models.cue imports and references something like GraphTooltipOptions in CUE, # models.cue imports and references something like VizTooltipOptions in CUE,
# it's still the same identifier as appeared in the original core models.cue # it's still the same identifier as appeared in the original core models.cue
# files, AND therefore is exactly the identifier that appears in # files, AND therefore is exactly the identifier that appears in
# cuetsy-generated @grafana/{ui,data} packages. That is, as long as we preserve # cuetsy-generated @grafana/{ui,data} packages. That is, as long as we preserve
# the relation between the identifier "GraphTooltipOptions" as a top-level # the relation between the identifier "VizTooltipOptions" as a top-level
# importable thing at all stages on the CUE side, then everything on the # importable thing at all stages on the CUE side, then everything on the
# TypeScript side will line up. # TypeScript side will line up.
sed -i -e 's/^import.*//g' {cue/ui/gen.cue,cue/data/gen.cue} sed -i -e 's/^import.*//g' {cue/ui/gen.cue,cue/data/gen.cue}