mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TimeSeries: Support vertical orientation (#83272)
* Time series with flipped axes * Ass prop and fix timezone labes * Remove not needed css * Fix zoomplugin * Update comment * Fix zoomplugin * Update * Update TooltipPlugin2 * Update variable names * Update naming and comments * Update public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx Co-authored-by: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> --------- Co-authored-by: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com>
This commit is contained in:
parent
8ed932bb60
commit
a76e21c837
@ -15,6 +15,7 @@ export const pluginVersion = "11.0.0-pre";
|
||||
|
||||
export interface Options extends common.OptionsWithTimezones {
|
||||
legend: common.VizLegendOptions;
|
||||
orientation?: common.VizOrientation;
|
||||
tooltip: common.VizTooltipOptions;
|
||||
}
|
||||
|
||||
|
@ -9,12 +9,13 @@
|
||||
background: rgba(120, 120, 130, 0.2);
|
||||
}
|
||||
|
||||
.u-cursor-x {
|
||||
.u-hz .u-cursor-x,
|
||||
.u-vt .u-cursor-y {
|
||||
border-right: 1px dashed rgba(120, 120, 130, 0.5);
|
||||
}
|
||||
|
||||
.u-cursor-y {
|
||||
width: 100%;
|
||||
.u-hz .u-cursor-y,
|
||||
.u-vt .u-cursor-x {
|
||||
border-bottom: 1px dashed rgba(120, 120, 130, 0.5);
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
TimeRange,
|
||||
TimeZone,
|
||||
} from '@grafana/data';
|
||||
import { AxisPlacement } from '@grafana/schema';
|
||||
import { AxisPlacement, VizOrientation } from '@grafana/schema';
|
||||
|
||||
import { FacetedData, PlotConfig, PlotTooltipInterpolator } from '../types';
|
||||
import { DEFAULT_PLOT_CONFIG, getStackingBands, pluginLog, StackingGroup } from '../utils';
|
||||
@ -314,6 +314,7 @@ type UPlotConfigPrepOpts<T extends Record<string, unknown> = {}> = {
|
||||
// Identifies the shared key for uPlot cursor sync
|
||||
eventsScope?: string;
|
||||
hoverProximity?: number;
|
||||
orientation?: VizOrientation;
|
||||
} & T;
|
||||
|
||||
/** @alpha */
|
||||
|
@ -282,7 +282,14 @@ export const TooltipPlugin2 = ({
|
||||
u.over.addEventListener('click', (e) => {
|
||||
if (e.target === u.over) {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
let xVal = u.posToVal(u.cursor.left!, 'x');
|
||||
let xVal;
|
||||
|
||||
const isXAxisHorizontal = u.scales.x.ori === 0;
|
||||
if (isXAxisHorizontal) {
|
||||
xVal = u.posToVal(u.cursor.left!, 'x');
|
||||
} else {
|
||||
xVal = u.posToVal(u.select.top + u.select.height, 'x');
|
||||
}
|
||||
|
||||
selectedRange = {
|
||||
from: xVal,
|
||||
@ -301,14 +308,19 @@ export const TooltipPlugin2 = ({
|
||||
});
|
||||
|
||||
config.addHook('setSelect', (u) => {
|
||||
const isXAxisHorizontal = u.scales.x.ori === 0;
|
||||
if (!viaSync && (clientZoom || queryZoom != null)) {
|
||||
if (maybeZoomAction(u.cursor!.event)) {
|
||||
if (clientZoom && yDrag) {
|
||||
if (u.select.height >= MIN_ZOOM_DIST) {
|
||||
for (let key in u.scales!) {
|
||||
if (key !== 'x') {
|
||||
const maxY = u.posToVal(u.select.top, key);
|
||||
const minY = u.posToVal(u.select.top + u.select.height, key);
|
||||
const maxY = isXAxisHorizontal
|
||||
? u.posToVal(u.select.top, key)
|
||||
: u.posToVal(u.select.left + u.select.width, key);
|
||||
const minY = isXAxisHorizontal
|
||||
? u.posToVal(u.select.top + u.select.height, key)
|
||||
: u.posToVal(u.select.left, key);
|
||||
|
||||
u.setScale(key, { min: minY, max: maxY });
|
||||
}
|
||||
@ -320,8 +332,12 @@ export const TooltipPlugin2 = ({
|
||||
yDrag = false;
|
||||
} else if (queryZoom != null) {
|
||||
if (u.select.width >= MIN_ZOOM_DIST) {
|
||||
const minX = u.posToVal(u.select.left, 'x');
|
||||
const maxX = u.posToVal(u.select.left + u.select.width, 'x');
|
||||
const minX = isXAxisHorizontal
|
||||
? u.posToVal(u.select.left, 'x')
|
||||
: u.posToVal(u.select.top + u.select.height, 'x');
|
||||
const maxX = isXAxisHorizontal
|
||||
? u.posToVal(u.select.left + u.select.width, 'x')
|
||||
: u.posToVal(u.select.top, 'x');
|
||||
|
||||
queryZoom({ from: minX, to: maxX });
|
||||
|
||||
@ -330,8 +346,8 @@ export const TooltipPlugin2 = ({
|
||||
}
|
||||
} else {
|
||||
selectedRange = {
|
||||
from: u.posToVal(u.select.left!, 'x'),
|
||||
to: u.posToVal(u.select.left! + u.select.width, 'x'),
|
||||
from: isXAxisHorizontal ? u.posToVal(u.select.left!, 'x') : u.posToVal(u.select.top + u.select.height, 'x'),
|
||||
to: isXAxisHorizontal ? u.posToVal(u.select.left! + u.select.width, 'x') : u.posToVal(u.select.top, 'x'),
|
||||
};
|
||||
|
||||
scheduleRender(true);
|
||||
|
@ -51,14 +51,18 @@ export const ZoomPlugin = ({ onZoom, config, withZoomY = false }: ZoomPluginProp
|
||||
}
|
||||
|
||||
config.addHook('setSelect', (u) => {
|
||||
const isXAxisHorizontal = u.scales.x.ori === 0;
|
||||
if (maybeZoomAction(u.cursor!.event)) {
|
||||
if (withZoomY && yDrag) {
|
||||
if (u.select.height >= MIN_ZOOM_DIST) {
|
||||
for (let key in u.scales!) {
|
||||
if (key !== 'x') {
|
||||
const maxY = u.posToVal(u.select.top, key);
|
||||
const minY = u.posToVal(u.select.top + u.select.height, key);
|
||||
|
||||
const maxY = isXAxisHorizontal
|
||||
? u.posToVal(u.select.top, key)
|
||||
: u.posToVal(u.select.left + u.select.width, key);
|
||||
const minY = isXAxisHorizontal
|
||||
? u.posToVal(u.select.top + u.select.height, key)
|
||||
: u.posToVal(u.select.left, key);
|
||||
u.setScale(key, { min: minY, max: maxY });
|
||||
}
|
||||
}
|
||||
@ -69,8 +73,12 @@ export const ZoomPlugin = ({ onZoom, config, withZoomY = false }: ZoomPluginProp
|
||||
yDrag = false;
|
||||
} else {
|
||||
if (u.select.width >= MIN_ZOOM_DIST) {
|
||||
const minX = u.posToVal(u.select.left, 'x');
|
||||
const maxX = u.posToVal(u.select.left + u.select.width, 'x');
|
||||
const minX = isXAxisHorizontal
|
||||
? u.posToVal(u.select.left, 'x')
|
||||
: u.posToVal(u.select.top + u.select.height, 'x');
|
||||
const maxX = isXAxisHorizontal
|
||||
? u.posToVal(u.select.left + u.select.width, 'x')
|
||||
: u.posToVal(u.select.top, 'x');
|
||||
|
||||
onZoom({ from: minX, to: maxX });
|
||||
|
||||
|
@ -35,6 +35,7 @@ export class UnthemedTimeSeries extends Component<TimeSeriesProps> {
|
||||
tweakAxis,
|
||||
eventsScope,
|
||||
hoverProximity: options?.tooltip?.hoverProximity,
|
||||
orientation: options?.orientation,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
GraphTransform,
|
||||
AxisColorMode,
|
||||
GraphGradientMode,
|
||||
VizOrientation,
|
||||
} from '@grafana/schema';
|
||||
|
||||
// unit lookup needed to determine if we want power-of-2 or power-of-10 axis ticks
|
||||
@ -90,7 +91,10 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
||||
tweakAxis = (opts) => opts,
|
||||
eventsScope = '__global_',
|
||||
hoverProximity,
|
||||
orientation = VizOrientation.Horizontal,
|
||||
}) => {
|
||||
// we want the Auto and Horizontal orientation to default to Horizontal
|
||||
const isHorizontal = orientation !== VizOrientation.Vertical;
|
||||
const builder = new UPlotConfigBuilder(timeZones[0]);
|
||||
|
||||
let alignedFrame: DataFrame;
|
||||
@ -113,15 +117,19 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
||||
let yScaleKey = '';
|
||||
|
||||
const xFieldAxisPlacement =
|
||||
xField.config.custom?.axisPlacement !== AxisPlacement.Hidden ? AxisPlacement.Bottom : AxisPlacement.Hidden;
|
||||
xField.config.custom?.axisPlacement === AxisPlacement.Hidden
|
||||
? AxisPlacement.Hidden
|
||||
: isHorizontal
|
||||
? AxisPlacement.Bottom
|
||||
: AxisPlacement.Left;
|
||||
const xFieldAxisShow = xField.config.custom?.axisPlacement !== AxisPlacement.Hidden;
|
||||
|
||||
if (xField.type === FieldType.time) {
|
||||
xScaleUnit = 'time';
|
||||
builder.addScale({
|
||||
scaleKey: xScaleKey,
|
||||
orientation: ScaleOrientation.Horizontal,
|
||||
direction: ScaleDirection.Right,
|
||||
orientation: isHorizontal ? ScaleOrientation.Horizontal : ScaleOrientation.Vertical,
|
||||
direction: isHorizontal ? ScaleDirection.Right : ScaleDirection.Up,
|
||||
isTime: true,
|
||||
range: () => {
|
||||
const r = getTimeRange();
|
||||
@ -133,7 +141,10 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
||||
const filterTicks: uPlot.Axis.Filter | undefined =
|
||||
timeZones.length > 1
|
||||
? (u, splits) => {
|
||||
return splits.map((v, i) => (i < 2 ? null : v));
|
||||
if (isHorizontal) {
|
||||
return splits.map((v, i) => (i < 2 ? null : v));
|
||||
}
|
||||
return splits;
|
||||
}
|
||||
: undefined;
|
||||
|
||||
@ -157,13 +168,12 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
||||
builder.addHook('drawAxes', (u: uPlot) => {
|
||||
u.ctx.save();
|
||||
|
||||
u.ctx.fillStyle = theme.colors.text.primary;
|
||||
u.ctx.textAlign = 'left';
|
||||
u.ctx.textBaseline = 'bottom';
|
||||
|
||||
let i = 0;
|
||||
u.axes.forEach((a) => {
|
||||
if (a.side === 2) {
|
||||
if (isHorizontal && a.side === 2) {
|
||||
u.ctx.fillStyle = theme.colors.text.primary;
|
||||
u.ctx.textAlign = 'left';
|
||||
u.ctx.textBaseline = 'bottom';
|
||||
//@ts-ignore
|
||||
let cssBaseline: number = a._pos + a._size;
|
||||
u.ctx.fillText(timeZones[i], u.bbox.left, cssBaseline * uPlot.pxRatio);
|
||||
@ -182,8 +192,8 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
||||
|
||||
builder.addScale({
|
||||
scaleKey: xScaleKey,
|
||||
orientation: ScaleOrientation.Horizontal,
|
||||
direction: ScaleDirection.Right,
|
||||
orientation: isHorizontal ? ScaleOrientation.Horizontal : ScaleOrientation.Vertical,
|
||||
direction: isHorizontal ? ScaleDirection.Right : ScaleDirection.Up,
|
||||
range: (u, dataMin, dataMax) => [xField.config.min ?? dataMin, xField.config.max ?? dataMax],
|
||||
});
|
||||
|
||||
@ -243,8 +253,8 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
||||
tweakScale(
|
||||
{
|
||||
scaleKey,
|
||||
orientation: ScaleOrientation.Vertical,
|
||||
direction: ScaleDirection.Up,
|
||||
orientation: isHorizontal ? ScaleOrientation.Vertical : ScaleOrientation.Horizontal,
|
||||
direction: isHorizontal ? ScaleDirection.Up : ScaleDirection.Right,
|
||||
distribution: customConfig.scaleDistribution?.type,
|
||||
log: customConfig.scaleDistribution?.log,
|
||||
linearThreshold: customConfig.scaleDistribution?.linearThreshold,
|
||||
@ -329,7 +339,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
|
||||
scaleKey,
|
||||
label: customConfig.axisLabel,
|
||||
size: customConfig.axisWidth,
|
||||
placement: customConfig.axisPlacement ?? AxisPlacement.Auto,
|
||||
placement: isHorizontal ? customConfig.axisPlacement ?? AxisPlacement.Auto : AxisPlacement.Bottom,
|
||||
formatValue: (v, decimals) => formattedValueToString(fmt(v, decimals)),
|
||||
theme,
|
||||
grid: { show: customConfig.axisGridShow },
|
||||
|
@ -2,7 +2,7 @@ import React, { useMemo, useState, useCallback } from 'react';
|
||||
|
||||
import { PanelProps, DataFrameType, DashboardCursorSync } from '@grafana/data';
|
||||
import { PanelDataErrorView } from '@grafana/runtime';
|
||||
import { TooltipDisplayMode } from '@grafana/schema';
|
||||
import { TooltipDisplayMode, VizOrientation } from '@grafana/schema';
|
||||
import { KeyboardPlugin, TooltipPlugin, TooltipPlugin2, usePanelContext, ZoomPlugin } from '@grafana/ui';
|
||||
import { TimeRange2, TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
|
||||
import { TimeSeries } from 'app/core/components/TimeSeries/TimeSeries';
|
||||
@ -36,7 +36,9 @@ export const TimeSeriesPanel = ({
|
||||
}: TimeSeriesPanelProps) => {
|
||||
const { sync, canAddAnnotations, onThresholdsChange, canEditThresholds, showThresholds, dataLinkPostProcessor } =
|
||||
usePanelContext();
|
||||
|
||||
// Vertical orientation is not available for users through config.
|
||||
// It is simplified version of horizontal time series panel and it does not support all plugins.
|
||||
const isVerticallyOriented = options.orientation === VizOrientation.Vertical;
|
||||
const frames = useMemo(() => prepareGraphableFields(data.series, config.theme2, timeRange), [data.series, timeRange]);
|
||||
const timezones = useMemo(() => getTimezones(options.timezone, timeZone), [options.timezone, timeZone]);
|
||||
const suggestions = useMemo(() => {
|
||||
@ -169,7 +171,7 @@ export const TimeSeriesPanel = ({
|
||||
</>
|
||||
)}
|
||||
{/* Renders annotation markers*/}
|
||||
{showNewVizTooltips ? (
|
||||
{!isVerticallyOriented && showNewVizTooltips ? (
|
||||
<AnnotationsPlugin2
|
||||
annotations={data.annotations ?? []}
|
||||
config={uplotConfig}
|
||||
@ -178,6 +180,7 @@ export const TimeSeriesPanel = ({
|
||||
setNewRange={setNewAnnotationRange}
|
||||
/>
|
||||
) : (
|
||||
!isVerticallyOriented &&
|
||||
data.annotations && (
|
||||
<AnnotationsPlugin annotations={data.annotations} config={uplotConfig} timeZone={timeZone} />
|
||||
)
|
||||
@ -185,7 +188,7 @@ export const TimeSeriesPanel = ({
|
||||
|
||||
{/*Enables annotations creation*/}
|
||||
{!showNewVizTooltips ? (
|
||||
enableAnnotationCreation ? (
|
||||
enableAnnotationCreation && !isVerticallyOriented ? (
|
||||
<AnnotationEditorPlugin data={alignedDataFrame} timeZone={timeZone} config={uplotConfig}>
|
||||
{({ startAnnotating }) => {
|
||||
return (
|
||||
@ -226,7 +229,7 @@ export const TimeSeriesPanel = ({
|
||||
/>
|
||||
)
|
||||
) : undefined}
|
||||
{data.annotations && (
|
||||
{data.annotations && !isVerticallyOriented && (
|
||||
<ExemplarsPlugin
|
||||
visibleSeries={getVisibleLabels(uplotConfig, frames)}
|
||||
config={uplotConfig}
|
||||
@ -235,7 +238,7 @@ export const TimeSeriesPanel = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{((canEditThresholds && onThresholdsChange) || showThresholds) && (
|
||||
{((canEditThresholds && onThresholdsChange) || showThresholds) && !isVerticallyOriented && (
|
||||
<ThresholdControlsPlugin
|
||||
config={uplotConfig}
|
||||
fieldConfig={fieldConfig}
|
||||
|
@ -23,8 +23,9 @@ composableKinds: PanelCfg: lineage: {
|
||||
version: [0, 0]
|
||||
schema: {
|
||||
Options: common.OptionsWithTimezones & {
|
||||
legend: common.VizLegendOptions
|
||||
tooltip: common.VizTooltipOptions
|
||||
legend: common.VizLegendOptions
|
||||
tooltip: common.VizTooltipOptions
|
||||
orientation?: common.VizOrientation
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
FieldConfig: common.GraphFieldConfig & {} @cuetsy(kind="interface")
|
||||
|
@ -12,6 +12,7 @@ import * as common from '@grafana/schema';
|
||||
|
||||
export interface Options extends common.OptionsWithTimezones {
|
||||
legend: common.VizLegendOptions;
|
||||
orientation?: common.VizOrientation;
|
||||
tooltip: common.VizTooltipOptions;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user