mirror of https://github.com/grafana/grafana
Explore: Context tooltip to copy labels and values from graph (#21405)
parent
26f72ccc4e
commit
c738a889ed
@ -0,0 +1,8 @@ |
||||
export interface FlotDataPoint { |
||||
dataIndex: number; |
||||
datapoint: number[]; |
||||
pageX: number; |
||||
pageY: number; |
||||
series: any; |
||||
seriesIndex: number; |
||||
} |
@ -0,0 +1,99 @@ |
||||
import React, { useContext } from 'react'; |
||||
import { ContextMenu, ContextMenuProps } from '../ContextMenu/ContextMenu'; |
||||
import { ThemeContext } from '../../themes'; |
||||
import { SeriesIcon } from '../Legend/SeriesIcon'; |
||||
import { GraphDimensions } from './GraphTooltip/types'; |
||||
import { |
||||
DateTimeInput, |
||||
FlotDataPoint, |
||||
getValueFromDimension, |
||||
getDisplayProcessor, |
||||
formattedValueToString, |
||||
Dimensions, |
||||
MS_DATE_TIME_FORMAT, |
||||
DEFAULT_DATE_TIME_FORMAT, |
||||
} from '@grafana/data'; |
||||
import { css } from 'emotion'; |
||||
|
||||
export type ContextDimensions<T extends Dimensions = any> = { [key in keyof T]: [number, number | undefined] | null }; |
||||
|
||||
export type GraphContextMenuProps = ContextMenuProps & { |
||||
getContextMenuSource: () => FlotDataPoint | null; |
||||
formatSourceDate: (date: DateTimeInput, format?: string) => string; |
||||
dimensions?: GraphDimensions; |
||||
contextDimensions?: ContextDimensions; |
||||
}; |
||||
|
||||
export const GraphContextMenu: React.FC<GraphContextMenuProps> = ({ |
||||
getContextMenuSource, |
||||
formatSourceDate, |
||||
items, |
||||
dimensions, |
||||
contextDimensions, |
||||
...otherProps |
||||
}) => { |
||||
const theme = useContext(ThemeContext); |
||||
const source = getContextMenuSource(); |
||||
|
||||
// Do not render items that do not have label specified
|
||||
const itemsToRender = items |
||||
? items.map(group => ({ |
||||
...group, |
||||
items: group.items.filter(item => item.label), |
||||
})) |
||||
: []; |
||||
|
||||
const renderHeader = () => { |
||||
if (!source) { |
||||
return null; |
||||
} |
||||
|
||||
// If dimensions supplied, we can calculate and display value
|
||||
let value; |
||||
if (dimensions?.yAxis && contextDimensions?.yAxis?.[1]) { |
||||
const valueFromDimensions = getValueFromDimension( |
||||
dimensions.yAxis, |
||||
contextDimensions.yAxis[0], |
||||
contextDimensions.yAxis[1] |
||||
); |
||||
const display = source.series.valueField.display ?? getDisplayProcessor({ field: source.series.valueField }); |
||||
value = display(valueFromDimensions); |
||||
} |
||||
|
||||
const timeFormat = source.series.hasMsResolution ? MS_DATE_TIME_FORMAT : DEFAULT_DATE_TIME_FORMAT; |
||||
return ( |
||||
<div |
||||
className={css` |
||||
padding: ${theme.spacing.xs} ${theme.spacing.sm}; |
||||
font-size: ${theme.typography.size.sm}; |
||||
z-index: ${theme.zIndex.tooltip}; |
||||
`}
|
||||
> |
||||
<strong>{formatSourceDate(source.datapoint[0], timeFormat)}</strong> |
||||
<div> |
||||
<SeriesIcon color={source.series.color} /> |
||||
<span |
||||
className={css` |
||||
white-space: nowrap; |
||||
padding-left: ${theme.spacing.xs}; |
||||
`}
|
||||
> |
||||
{source.series.alias || source.series.label} |
||||
</span> |
||||
{value && ( |
||||
<span |
||||
className={css` |
||||
white-space: nowrap; |
||||
padding-left: ${theme.spacing.md}; |
||||
`}
|
||||
> |
||||
{formattedValueToString(value)} |
||||
</span> |
||||
)} |
||||
</div> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
return <ContextMenu {...otherProps} items={itemsToRender} renderHeader={renderHeader} />; |
||||
}; |
@ -1,62 +0,0 @@ |
||||
import React, { useContext } from 'react'; |
||||
import { FlotDataPoint } from './GraphContextMenuCtrl'; |
||||
import { ContextMenu, ContextMenuProps, SeriesIcon, ThemeContext } from '@grafana/ui'; |
||||
import { DateTimeInput } from '@grafana/data'; |
||||
import { css } from 'emotion'; |
||||
|
||||
type GraphContextMenuProps = ContextMenuProps & { |
||||
getContextMenuSource: () => FlotDataPoint | null; |
||||
formatSourceDate: (date: DateTimeInput, format?: string) => string; |
||||
}; |
||||
|
||||
export const GraphContextMenu: React.FC<GraphContextMenuProps> = ({ |
||||
getContextMenuSource, |
||||
formatSourceDate, |
||||
items, |
||||
...otherProps |
||||
}) => { |
||||
const theme = useContext(ThemeContext); |
||||
const source = getContextMenuSource(); |
||||
|
||||
// Do not render items that do not have label specified
|
||||
const itemsToRender = items |
||||
? items.map(group => ({ |
||||
...group, |
||||
items: group.items.filter(item => item.label), |
||||
})) |
||||
: []; |
||||
|
||||
const renderHeader = source |
||||
? () => { |
||||
if (!source) { |
||||
return null; |
||||
} |
||||
|
||||
const timeFormat = source.series.hasMsResolution ? 'YYYY-MM-DD HH:mm:ss.SSS' : 'YYYY-MM-DD HH:mm:ss'; |
||||
|
||||
return ( |
||||
<div |
||||
className={css` |
||||
padding: ${theme.spacing.xs} ${theme.spacing.sm}; |
||||
font-size: ${theme.typography.size.sm}; |
||||
`}
|
||||
> |
||||
<strong>{formatSourceDate(source.datapoint[0], timeFormat)}</strong> |
||||
<div> |
||||
<SeriesIcon color={source.series.color} /> |
||||
<span |
||||
className={css` |
||||
white-space: nowrap; |
||||
padding-left: ${theme.spacing.xs}; |
||||
`}
|
||||
> |
||||
{source.series.alias} |
||||
</span> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
: null; |
||||
|
||||
return <ContextMenu {...otherProps} items={itemsToRender} renderHeader={renderHeader} />; |
||||
}; |
Loading…
Reference in new issue