mirror of
https://github.com/grafana/grafana.git
synced 2024-11-26 02:40:26 -06:00
Timeseries Panel: render threshold at either top or bottom of graph when it is out… (#41649)
This commit is contained in:
parent
20e1457a78
commit
3e16abc939
@ -54,16 +54,15 @@ export const ThresholdControlsPlugin: React.FC<ThresholdControlsPluginProps> = (
|
||||
if (Number.isNaN(yPos) || !Number.isFinite(yPos)) {
|
||||
continue;
|
||||
}
|
||||
if (yPos < 0 || yPos > plot.bbox.height / window.devicePixelRatio) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const height = plot.bbox.height / window.devicePixelRatio;
|
||||
|
||||
const handle = (
|
||||
<ThresholdDragHandle
|
||||
key={`${step.value}-${i}`}
|
||||
step={step}
|
||||
y={yPos}
|
||||
dragBounds={{ top: 0, bottom: plot.bbox.height / window.devicePixelRatio }}
|
||||
dragBounds={{ top: 0, bottom: height }}
|
||||
mapPositionToValue={(y) => plot.posToVal(y, scale)}
|
||||
formatValue={(v) => getValueFormat(scale)(v, decimals).text}
|
||||
onChange={(value) => {
|
||||
@ -80,6 +79,7 @@ export const ThresholdControlsPlugin: React.FC<ThresholdControlsPluginProps> = (
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { Threshold, GrafanaTheme2 } from '@grafana/data';
|
||||
import { useStyles2, useTheme2 } from '@grafana/ui';
|
||||
import Draggable, { DraggableBounds } from 'react-draggable';
|
||||
|
||||
type OutOfBounds = 'top' | 'bottom' | 'none';
|
||||
|
||||
interface ThresholdDragHandleProps {
|
||||
step: Threshold;
|
||||
y: number;
|
||||
@ -22,10 +24,33 @@ export const ThresholdDragHandle: React.FC<ThresholdDragHandleProps> = ({
|
||||
onChange,
|
||||
}) => {
|
||||
const theme = useTheme2();
|
||||
const styles = useStyles2(getStyles);
|
||||
let yPos = y;
|
||||
let outOfBounds: OutOfBounds = 'none';
|
||||
|
||||
if (y < (dragBounds.top ?? 0)) {
|
||||
outOfBounds = 'top';
|
||||
}
|
||||
|
||||
// there seems to be a 22px offset at the bottom where the threshold line is still drawn
|
||||
// this is probably offset by the size of the x-axis component
|
||||
if (y > (dragBounds.bottom ?? 0) + 22) {
|
||||
outOfBounds = 'bottom';
|
||||
}
|
||||
|
||||
if (outOfBounds === 'bottom') {
|
||||
yPos = dragBounds.bottom ?? y;
|
||||
}
|
||||
|
||||
if (outOfBounds === 'top') {
|
||||
yPos = dragBounds.top ?? y;
|
||||
}
|
||||
|
||||
const styles = useStyles2((theme) => getStyles(theme, step, outOfBounds));
|
||||
const [currentValue, setCurrentValue] = useState(step.value);
|
||||
const bgColor = theme.visualization.getColorByName(step.color);
|
||||
const textColor = theme.colors.getContrastText(bgColor);
|
||||
|
||||
const textColor = useMemo(() => {
|
||||
return theme.colors.getContrastText(theme.visualization.getColorByName(step.color));
|
||||
}, [step.color, theme]);
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
@ -37,13 +62,10 @@ export const ThresholdDragHandle: React.FC<ThresholdDragHandleProps> = ({
|
||||
return false;
|
||||
}}
|
||||
onDrag={(_e, d) => setCurrentValue(mapPositionToValue(d.lastY))}
|
||||
position={{ x: 0, y }}
|
||||
position={{ x: 0, y: yPos }}
|
||||
bounds={dragBounds}
|
||||
>
|
||||
<div
|
||||
className={styles.handle}
|
||||
style={{ color: textColor, background: bgColor, borderColor: bgColor, borderWidth: 0 }}
|
||||
>
|
||||
<div className={styles.handle} style={{ color: textColor }}>
|
||||
<span className={styles.handleText}>{formatValue(currentValue)}</span>
|
||||
</div>
|
||||
</Draggable>
|
||||
@ -52,32 +74,38 @@ export const ThresholdDragHandle: React.FC<ThresholdDragHandleProps> = ({
|
||||
|
||||
ThresholdDragHandle.displayName = 'ThresholdDragHandle';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
const getStyles = (theme: GrafanaTheme2, step: Threshold, outOfBounds: OutOfBounds) => {
|
||||
const mainColor = theme.visualization.getColorByName(step.color);
|
||||
const arrowStyles = getArrowStyles(outOfBounds);
|
||||
const isOutOfBounds = outOfBounds !== 'none';
|
||||
|
||||
return {
|
||||
handle: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: calc(100% - 9px);
|
||||
height: 18px;
|
||||
margin-left: 9px;
|
||||
margin-top: -9px;
|
||||
border-color: ${mainColor};
|
||||
cursor: grab;
|
||||
border-top-right-radius: ${theme.shape.borderRadius(1)};
|
||||
border-bottom-right-radius: ${theme.shape.borderRadius(1)};
|
||||
${isOutOfBounds &&
|
||||
css`
|
||||
margin-top: 0;
|
||||
border-radius: ${theme.shape.borderRadius(1)};
|
||||
`}
|
||||
background: ${mainColor};
|
||||
font-size: ${theme.typography.bodySmall.fontSize};
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -9px;
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-right-style: solid;
|
||||
border-right-width: 9px;
|
||||
border-right-color: inherit;
|
||||
border-top: 9px solid transparent;
|
||||
border-bottom: 9px solid transparent;
|
||||
${arrowStyles};
|
||||
}
|
||||
`,
|
||||
handleText: css`
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@ -85,3 +113,51 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
function getArrowStyles(outOfBounds: OutOfBounds) {
|
||||
const inBounds = outOfBounds === 'none';
|
||||
|
||||
const triangle = (size: number) => css`
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
left: 0;
|
||||
|
||||
border-right-style: solid;
|
||||
border-right-width: ${size}px;
|
||||
border-right-color: inherit;
|
||||
border-top: ${size}px solid transparent;
|
||||
border-bottom: ${size}px solid transparent;
|
||||
`;
|
||||
|
||||
if (inBounds) {
|
||||
return css`
|
||||
${triangle(9)};
|
||||
left: -9px;
|
||||
`;
|
||||
}
|
||||
|
||||
if (outOfBounds === 'top') {
|
||||
return css`
|
||||
${triangle(5)};
|
||||
left: calc(50% - 2.5px);
|
||||
top: -7px;
|
||||
transform: rotate(90deg);
|
||||
`;
|
||||
}
|
||||
|
||||
if (outOfBounds === 'bottom') {
|
||||
return css`
|
||||
${triangle(5)};
|
||||
left: calc(50% - 2.5px);
|
||||
top: calc(100% - 2.5px);
|
||||
transform: rotate(-90deg);
|
||||
`;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user