2025-04-01 10:38:02 +09:00

152 lines
4.6 KiB
TypeScript

import { css } from '@emotion/css';
import { useMemo } from 'react';
import { FALLBACK_COLOR, PanelProps } from '@grafana/data';
import { alpha } from '@grafana/data/src/themes/colorManipulator';
import { config } from '@grafana/runtime';
import {
TooltipDisplayMode,
TooltipPlugin2,
UPlotChart,
VizLayout,
VizLegend,
VizLegendItem,
useStyles2,
useTheme2,
} from '@grafana/ui';
import { TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
import { getDisplayValuesForCalcs } from '@grafana/ui/src/components/uPlot/utils';
import { getDataLinks } from '../status-history/utils';
import { XYChartTooltip } from './XYChartTooltip';
import { Options } from './panelcfg.gen';
import { prepConfig } from './scatter';
import { prepSeries } from './utils';
type Props2 = PanelProps<Options>;
export const XYChartPanel2 = (props: Props2) => {
const styles = useStyles2(getStyles);
const theme = useTheme2();
let { mapping, series: mappedSeries } = props.options;
// regenerate series schema when mappings or data changes
let series = useMemo(
() => prepSeries(mapping, mappedSeries, props.data.series, props.fieldConfig),
// eslint-disable-next-line react-hooks/exhaustive-deps
[mapping, mappedSeries, props.data.series, props.fieldConfig]
);
// if series changed due to mappings or data structure, re-init config & renderers
let { builder, prepData } = useMemo(
() => prepConfig(series, config.theme2),
// eslint-disable-next-line react-hooks/exhaustive-deps
[mapping, mappedSeries, props.data.structureRev, props.fieldConfig, props.options.tooltip]
);
// generate data struct for uPlot mode: 2
let data = useMemo(
() => prepData(series),
// eslint-disable-next-line react-hooks/exhaustive-deps
[series]
);
// todo: handle errors
let error = builder == null || data.length === 0 ? 'Err' : '';
// TODO: React.memo()
const renderLegend = () => {
if (!props.options.legend.showLegend) {
return null;
}
const items: VizLegendItem[] = [];
series.forEach((s, idx) => {
let yField = s.y.field;
let config = yField.config;
let custom = config.custom;
if (!custom.hideFrom?.legend) {
items.push({
yAxis: 1, // TODO: pull from y field
label: s.name.value,
color: alpha(s.color.fixed ?? FALLBACK_COLOR, 1),
getItemKey: () => `${idx}-${s.name.value}`,
fieldName: yField.state?.displayName ?? yField.name,
disabled: yField.state?.hideFrom?.viz ?? false,
getDisplayValues: () => getDisplayValuesForCalcs(props.options.legend.calcs, yField, theme),
});
}
});
const { placement, displayMode, width, sortBy, sortDesc } = props.options.legend;
return (
<VizLayout.Legend placement={placement} width={width}>
<VizLegend
className={styles.legend}
placement={placement}
items={items}
displayMode={displayMode}
sortBy={sortBy}
sortDesc={sortDesc}
isSortable={true}
/>
</VizLayout.Legend>
);
};
if (error) {
return (
<div className="panel-empty">
<p>{error}</p>
</div>
);
}
return (
<VizLayout width={props.width} height={props.height} legend={renderLegend()}>
{(vizWidth: number, vizHeight: number) => (
<UPlotChart config={builder!} data={data} width={vizWidth} height={vizHeight}>
{props.options.tooltip.mode !== TooltipDisplayMode.None && (
<TooltipPlugin2
config={builder!}
hoverMode={TooltipHoverMode.xyOne}
getDataLinks={(seriesIdx, dataIdx) => {
const xySeries = series[seriesIdx - 1];
return getDataLinks(xySeries.y.field, dataIdx);
}}
render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2, viaSync, dataLinks) => {
return (
<XYChartTooltip
data={props.data.series}
dataIdxs={dataIdxs}
xySeries={series}
dismiss={dismiss}
isPinned={isPinned}
seriesIdx={seriesIdx!}
replaceVariables={props.replaceVariables}
dataLinks={dataLinks}
/>
);
}}
maxWidth={props.options.tooltip.maxWidth}
/>
)}
</UPlotChart>
)}
</VizLayout>
);
};
const getStyles = () => ({
legend: css({
div: {
justifyContent: 'flex-start',
},
}),
});