XYPanel: Improvements (#54220)

* Fix panel option bugs and make tooltip options work

* Support multiple series in explicit mode. Rename XY/Explicit to Auto/Manual

* Fixes

* Fix

* Legend improvements

* Rewrite Pure Component to Function Component

* Add datalinks support

* Legend fixes and CR modifications

* Fix bugs that crash panel
This commit is contained in:
Victor Marin
2022-09-05 11:03:17 +03:00
committed by GitHub
parent 4952b7f22d
commit 6d2352735d
12 changed files with 653 additions and 247 deletions

View File

@@ -1,3 +1,4 @@
import { MutableRefObject } from 'react';
import uPlot from 'uplot';
import {
@@ -41,19 +42,26 @@ export function prepScatter(
options: XYChartOptions,
getData: () => DataFrame[],
theme: GrafanaTheme2,
ttip: ScatterHoverCallback
ttip: ScatterHoverCallback,
onUPlotClick: null | ((evt?: Object) => void),
isToolTipOpen: MutableRefObject<boolean>
): ScatterPanelInfo {
let series: ScatterSeries[];
let builder: UPlotConfigBuilder;
try {
series = prepSeries(options, getData());
builder = prepConfig(getData, series, theme, ttip);
builder = prepConfig(getData, series, theme, ttip, onUPlotClick, isToolTipOpen);
} catch (e) {
console.log('prepScatter ERROR', e);
const errorMessage = e instanceof Error ? e.message : 'Unknown error in prepScatter';
let errorMsg = 'Unknown error in prepScatter';
if (typeof e === 'string') {
errorMsg = e;
} else if (e instanceof Error) {
errorMsg = e.message;
}
return {
error: errorMessage,
error: errorMsg,
series: [],
};
}
@@ -120,7 +128,7 @@ function getScatterSeries(
// Size configs
//----------------
let pointSizeHints = dims.pointSizeConfig;
let pointSizeFixed = dims.pointSizeConfig?.fixed ?? y.config.custom?.pointSizeConfig?.fixed ?? 5;
let pointSizeFixed = dims.pointSizeConfig?.fixed ?? y.config.custom?.pointSize?.fixed ?? 5;
let pointSize: DimensionValues<number> = () => pointSizeFixed;
if (dims.pointSizeIndex) {
pointSize = (frame) => {
@@ -176,6 +184,7 @@ function getScatterSeries(
label: VisibilityMode.Never,
labelValue: () => '',
show: !frame.fields[yIndex].config.custom.hideFrom?.viz,
hints: {
pointSize: pointSizeHints!,
@@ -189,11 +198,13 @@ function getScatterSeries(
function prepSeries(options: XYChartOptions, frames: DataFrame[]): ScatterSeries[] {
let seriesIndex = 0;
if (!frames.length) {
throw 'missing data';
throw 'Missing data';
}
if (options.mode === 'explicit') {
if (options.mode === 'manual') {
if (options.series?.length) {
const scatterSeries: ScatterSeries[] = [];
for (const series of options.series) {
if (!series?.x) {
throw 'Select X dimension';
@@ -221,10 +232,12 @@ function prepSeries(options: XYChartOptions, frames: DataFrame[]): ScatterSeries
pointSizeConfig: series.pointSize,
pointSizeIndex: findFieldIndex(frame, series.pointSize?.field),
};
return [getScatterSeries(seriesIndex++, frames, frameIndex, xIndex, yIndex, dims)];
scatterSeries.push(getScatterSeries(seriesIndex++, frames, frameIndex, xIndex, yIndex, dims));
}
}
}
return scatterSeries;
}
}
@@ -232,7 +245,7 @@ function prepSeries(options: XYChartOptions, frames: DataFrame[]): ScatterSeries
const dims = options.dims ?? {};
const frameIndex = dims.frame ?? 0;
const frame = frames[frameIndex];
const numericIndicies: number[] = [];
const numericIndices: number[] = [];
let xIndex = findFieldIndex(frame, dims.x);
for (let i = 0; i < frame.fields.length; i++) {
@@ -245,7 +258,7 @@ function prepSeries(options: XYChartOptions, frames: DataFrame[]): ScatterSeries
continue; // skip
}
numericIndicies.push(i);
numericIndices.push(i);
}
}
@@ -253,10 +266,10 @@ function prepSeries(options: XYChartOptions, frames: DataFrame[]): ScatterSeries
throw 'Missing X dimension';
}
if (!numericIndicies.length) {
if (!numericIndices.length) {
throw 'No Y values';
}
return numericIndicies.map((yIndex) => getScatterSeries(seriesIndex++, frames, frameIndex, xIndex!, yIndex, {}));
return numericIndices.map((yIndex) => getScatterSeries(seriesIndex++, frames, frameIndex, xIndex!, yIndex, {}));
}
interface DrawBubblesOpts {
@@ -278,7 +291,9 @@ const prepConfig = (
getData: () => DataFrame[],
scatterSeries: ScatterSeries[],
theme: GrafanaTheme2,
ttip: ScatterHoverCallback
ttip: ScatterHoverCallback,
onUPlotClick: null | ((evt?: Object) => void),
isToolTipOpen: MutableRefObject<boolean>
) => {
let qt: Quadtree;
let hRect: Rect | null;
@@ -489,9 +504,32 @@ const prepConfig = (
},
});
const clearPopupIfOpened = () => {
if (isToolTipOpen.current) {
ttip(undefined);
if (onUPlotClick) {
onUPlotClick();
}
}
};
let ref_parent: HTMLElement | null = null;
// clip hover points/bubbles to plotting area
builder.addHook('init', (u, r) => {
u.over.style.overflow = 'hidden';
ref_parent = u.root.parentElement;
if (onUPlotClick) {
ref_parent?.addEventListener('click', onUPlotClick);
}
});
builder.addHook('destroy', (u) => {
if (onUPlotClick) {
ref_parent?.removeEventListener('click', onUPlotClick);
clearPopupIfOpened();
}
});
let rect: DOMRect;
@@ -502,11 +540,10 @@ const prepConfig = (
});
builder.addHook('setLegend', (u) => {
// console.log('TTIP???', u.cursor.idxs);
if (u.cursor.idxs != null) {
for (let i = 0; i < u.cursor.idxs.length; i++) {
const sel = u.cursor.idxs[i];
if (sel != null) {
if (sel != null && !isToolTipOpen.current) {
ttip({
scatterIndex: i - 1,
xIndex: sel,
@@ -517,10 +554,15 @@ const prepConfig = (
}
}
}
ttip(undefined);
if (!isToolTipOpen.current) {
ttip(undefined);
}
});
builder.addHook('drawClear', (u) => {
clearPopupIfOpened();
qt = qt || new Quadtree(0, 0, u.bbox.width, u.bbox.height);
qt.clear();
@@ -600,6 +642,7 @@ const prepConfig = (
scaleKey: '', // facets' scales used (above)
lineColor: lineColor as string,
fillColor: alpha(pointColor, 0.5),
show: !field.config.custom.hideFrom?.viz,
});
});
@@ -634,7 +677,7 @@ const prepConfig = (
* from? is this where we would support that? -- need the previous values
*/
export function prepData(info: ScatterPanelInfo, data: DataFrame[], from?: number): FacetedData {
if (info.error) {
if (info.error || !data.length) {
return [null];
}
return [