mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
BarChartPanel: Adds support for Tooltip in BarChartPanel (#33938)
* Adds support for Tooltip in BarChartPanel * Revert some formatting * Remove BarChart story
This commit is contained in:
parent
60c32dc96a
commit
bf2c45db01
@ -1,66 +0,0 @@
|
||||
import { toDataFrame, FieldType, VizOrientation } from '@grafana/data';
|
||||
import React from 'react';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { BarChart } from './BarChart';
|
||||
import { LegendDisplayMode } from '../VizLegend/models.gen';
|
||||
import { prepDataForStorybook } from '../../utils/storybook/data';
|
||||
import { useTheme2 } from '../../themes';
|
||||
import { select } from '@storybook/addon-knobs';
|
||||
import { BarChartOptions, BarValueVisibility } from './types';
|
||||
import { StackingMode } from '../uPlot/config';
|
||||
|
||||
export default {
|
||||
title: 'Visualizations/BarChart',
|
||||
component: BarChart,
|
||||
decorators: [withCenteredStory],
|
||||
parameters: {
|
||||
docs: {},
|
||||
},
|
||||
};
|
||||
|
||||
const getKnobs = () => {
|
||||
return {
|
||||
legendPlacement: select(
|
||||
'Legend placement',
|
||||
{
|
||||
bottom: 'bottom',
|
||||
right: 'right',
|
||||
},
|
||||
'bottom'
|
||||
),
|
||||
orientation: select(
|
||||
'Bar orientation',
|
||||
{
|
||||
vertical: VizOrientation.Vertical,
|
||||
horizontal: VizOrientation.Horizontal,
|
||||
},
|
||||
VizOrientation.Vertical
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const Basic: React.FC = () => {
|
||||
const { legendPlacement, orientation } = getKnobs();
|
||||
|
||||
const theme = useTheme2();
|
||||
const frame = toDataFrame({
|
||||
fields: [
|
||||
{ name: 'x', type: FieldType.string, values: ['group 1', 'group 2'] },
|
||||
{ name: 'a', type: FieldType.number, values: [10, 20] },
|
||||
{ name: 'b', type: FieldType.number, values: [30, 10] },
|
||||
],
|
||||
});
|
||||
|
||||
const data = prepDataForStorybook([frame], theme);
|
||||
|
||||
const options: BarChartOptions = {
|
||||
orientation: orientation,
|
||||
legend: { displayMode: LegendDisplayMode.List, placement: legendPlacement, calcs: [] },
|
||||
stacking: StackingMode.None,
|
||||
showValue: BarValueVisibility.Always,
|
||||
barWidth: 0.97,
|
||||
groupWidth: 0.7,
|
||||
};
|
||||
|
||||
return <BarChart data={data} width={600} height={400} {...options} />;
|
||||
};
|
@ -1,100 +1,52 @@
|
||||
import React from 'react';
|
||||
import { AlignedData } from 'uplot';
|
||||
import { DataFrame, TimeRange } from '@grafana/data';
|
||||
import { VizLayout } from '../VizLayout/VizLayout';
|
||||
import { Themeable2 } from '../../types';
|
||||
import { UPlotChart } from '../uPlot/Plot';
|
||||
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
|
||||
import { GraphNGLegendEvent } from '../GraphNG/types';
|
||||
import { BarChartOptions } from './types';
|
||||
import { withTheme2 } from '../../themes/ThemeContext';
|
||||
import { preparePlotConfigBuilder, preparePlotFrame } from './utils';
|
||||
import { pluginLog, preparePlotData } from '../uPlot/utils';
|
||||
import { LegendDisplayMode } from '../VizLegend/models.gen';
|
||||
import { PlotLegend } from '../uPlot/PlotLegend';
|
||||
import { GraphNG, GraphNGProps } from '../GraphNG/GraphNG';
|
||||
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface BarChartProps extends Themeable2, BarChartOptions {
|
||||
height: number;
|
||||
width: number;
|
||||
data: DataFrame[];
|
||||
structureRev?: number; // a number that will change when the data[] structure changes
|
||||
onLegendClick?: (event: GraphNGLegendEvent) => void;
|
||||
}
|
||||
export interface BarChartProps
|
||||
extends BarChartOptions,
|
||||
Omit<GraphNGProps, 'prepConfig' | 'propsToDiff' | 'renderLegend'> {}
|
||||
|
||||
interface BarChartState {
|
||||
data: AlignedData;
|
||||
alignedDataFrame: DataFrame;
|
||||
config?: UPlotConfigBuilder;
|
||||
}
|
||||
const propsToDiff: string[] = ['orientation', 'barWidth', 'groupWidth', 'showValue'];
|
||||
|
||||
class UnthemedBarChart extends React.Component<BarChartProps, BarChartState> {
|
||||
constructor(props: BarChartProps) {
|
||||
super(props);
|
||||
const alignedDataFrame = preparePlotFrame(props.data);
|
||||
if (!alignedDataFrame) {
|
||||
return;
|
||||
}
|
||||
const data = preparePlotData(alignedDataFrame);
|
||||
const config = preparePlotConfigBuilder(alignedDataFrame, this.props.theme, this.props);
|
||||
this.state = {
|
||||
alignedDataFrame,
|
||||
data,
|
||||
config,
|
||||
class UnthemedBarChart extends React.Component<BarChartProps> {
|
||||
prepConfig = (alignedFrame: DataFrame, getTimeRange: () => TimeRange) => {
|
||||
const { eventBus } = this.context;
|
||||
const { theme, timeZone, orientation, barWidth, showValue, groupWidth, stacking, legend, tooltip } = this.props;
|
||||
return preparePlotConfigBuilder({
|
||||
frame: alignedFrame,
|
||||
getTimeRange,
|
||||
theme,
|
||||
timeZone,
|
||||
eventBus,
|
||||
orientation,
|
||||
barWidth,
|
||||
showValue,
|
||||
groupWidth,
|
||||
stacking,
|
||||
legend,
|
||||
tooltip,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: BarChartProps) {
|
||||
const { data, orientation, groupWidth, barWidth, showValue, structureRev } = this.props;
|
||||
const { alignedDataFrame } = this.state;
|
||||
let shouldConfigUpdate = false;
|
||||
let stateUpdate = {} as BarChartState;
|
||||
|
||||
if (
|
||||
this.state.config === undefined ||
|
||||
orientation !== prevProps.orientation ||
|
||||
groupWidth !== prevProps.groupWidth ||
|
||||
barWidth !== prevProps.barWidth ||
|
||||
showValue !== prevProps.showValue
|
||||
) {
|
||||
shouldConfigUpdate = true;
|
||||
}
|
||||
|
||||
if (data !== prevProps.data || shouldConfigUpdate) {
|
||||
const hasStructureChanged = structureRev !== prevProps.structureRev || !structureRev;
|
||||
const alignedData = preparePlotFrame(data);
|
||||
|
||||
if (!alignedData) {
|
||||
return;
|
||||
}
|
||||
stateUpdate = {
|
||||
alignedDataFrame: alignedData,
|
||||
data: preparePlotData(alignedData),
|
||||
};
|
||||
if (shouldConfigUpdate || hasStructureChanged) {
|
||||
pluginLog('BarChart', false, 'updating config');
|
||||
const builder = preparePlotConfigBuilder(alignedDataFrame, this.props.theme, this.props);
|
||||
stateUpdate = { ...stateUpdate, config: builder };
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(stateUpdate).length > 0) {
|
||||
this.setState(stateUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
renderLegend() {
|
||||
const { legend, onLegendClick, data } = this.props;
|
||||
const { config } = this.state;
|
||||
renderLegend = (config: UPlotConfigBuilder) => {
|
||||
const { legend, onLegendClick, frames } = this.props;
|
||||
|
||||
if (!config || legend.displayMode === LegendDisplayMode.Hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<PlotLegend
|
||||
data={data}
|
||||
data={frames}
|
||||
config={config}
|
||||
onLegendClick={onLegendClick}
|
||||
maxHeight="35%"
|
||||
@ -102,31 +54,21 @@ class UnthemedBarChart extends React.Component<BarChartProps, BarChartState> {
|
||||
{...legend}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { width, height } = this.props;
|
||||
const { config, data } = this.state;
|
||||
|
||||
if (!config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<VizLayout width={width} height={height} legend={this.renderLegend()}>
|
||||
{(vizWidth: number, vizHeight: number) => (
|
||||
<UPlotChart
|
||||
data={data}
|
||||
config={config}
|
||||
width={vizWidth}
|
||||
height={vizHeight}
|
||||
timeRange={({ from: 1, to: 1 } as unknown) as TimeRange} // HACK
|
||||
<GraphNG
|
||||
{...this.props}
|
||||
frames={this.props.frames}
|
||||
prepConfig={this.prepConfig}
|
||||
propsToDiff={propsToDiff}
|
||||
preparePlotFrame={preparePlotFrame}
|
||||
renderLegend={this.renderLegend as any}
|
||||
/>
|
||||
)}
|
||||
</VizLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const BarChart = withTheme2(UnthemedBarChart);
|
||||
BarChart.displayName = 'GraphNG';
|
||||
BarChart.displayName = 'BarChart';
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`GraphNG utils preparePlotConfigBuilder orientation 1`] = `
|
||||
exports[`BarChart utils preparePlotConfigBuilder orientation 1`] = `
|
||||
Object {
|
||||
"axes": Array [
|
||||
Object {
|
||||
@ -61,13 +61,10 @@ Object {
|
||||
},
|
||||
"points": Object {
|
||||
"fill": [Function],
|
||||
"show": false,
|
||||
"size": [Function],
|
||||
"stroke": [Function],
|
||||
"width": [Function],
|
||||
},
|
||||
"x": false,
|
||||
"y": false,
|
||||
},
|
||||
"hooks": Object {
|
||||
"drawClear": Array [
|
||||
@ -76,9 +73,6 @@ Object {
|
||||
"init": Array [
|
||||
[Function],
|
||||
],
|
||||
"setCursor": Array [
|
||||
[Function],
|
||||
],
|
||||
},
|
||||
"scales": Object {
|
||||
"m/s": Object {
|
||||
@ -100,9 +94,7 @@ Object {
|
||||
"time": false,
|
||||
},
|
||||
},
|
||||
"select": Object {
|
||||
"show": false,
|
||||
},
|
||||
"select": undefined,
|
||||
"series": Array [
|
||||
Object {},
|
||||
Object {
|
||||
@ -126,7 +118,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`GraphNG utils preparePlotConfigBuilder orientation 2`] = `
|
||||
exports[`BarChart utils preparePlotConfigBuilder orientation 2`] = `
|
||||
Object {
|
||||
"axes": Array [
|
||||
Object {
|
||||
@ -187,13 +179,10 @@ Object {
|
||||
},
|
||||
"points": Object {
|
||||
"fill": [Function],
|
||||
"show": false,
|
||||
"size": [Function],
|
||||
"stroke": [Function],
|
||||
"width": [Function],
|
||||
},
|
||||
"x": false,
|
||||
"y": false,
|
||||
},
|
||||
"hooks": Object {
|
||||
"drawClear": Array [
|
||||
@ -202,9 +191,6 @@ Object {
|
||||
"init": Array [
|
||||
[Function],
|
||||
],
|
||||
"setCursor": Array [
|
||||
[Function],
|
||||
],
|
||||
},
|
||||
"scales": Object {
|
||||
"m/s": Object {
|
||||
@ -226,9 +212,7 @@ Object {
|
||||
"time": false,
|
||||
},
|
||||
},
|
||||
"select": Object {
|
||||
"show": false,
|
||||
},
|
||||
"select": undefined,
|
||||
"series": Array [
|
||||
Object {},
|
||||
Object {
|
||||
@ -252,7 +236,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`GraphNG utils preparePlotConfigBuilder orientation 3`] = `
|
||||
exports[`BarChart utils preparePlotConfigBuilder orientation 3`] = `
|
||||
Object {
|
||||
"axes": Array [
|
||||
Object {
|
||||
@ -313,13 +297,10 @@ Object {
|
||||
},
|
||||
"points": Object {
|
||||
"fill": [Function],
|
||||
"show": false,
|
||||
"size": [Function],
|
||||
"stroke": [Function],
|
||||
"width": [Function],
|
||||
},
|
||||
"x": false,
|
||||
"y": false,
|
||||
},
|
||||
"hooks": Object {
|
||||
"drawClear": Array [
|
||||
@ -328,9 +309,6 @@ Object {
|
||||
"init": Array [
|
||||
[Function],
|
||||
],
|
||||
"setCursor": Array [
|
||||
[Function],
|
||||
],
|
||||
},
|
||||
"scales": Object {
|
||||
"m/s": Object {
|
||||
@ -352,9 +330,7 @@ Object {
|
||||
"time": false,
|
||||
},
|
||||
},
|
||||
"select": Object {
|
||||
"show": false,
|
||||
},
|
||||
"select": undefined,
|
||||
"series": Array [
|
||||
Object {},
|
||||
Object {
|
||||
@ -378,7 +354,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`GraphNG utils preparePlotConfigBuilder stacking 1`] = `
|
||||
exports[`BarChart utils preparePlotConfigBuilder stacking 1`] = `
|
||||
Object {
|
||||
"axes": Array [
|
||||
Object {
|
||||
@ -439,13 +415,10 @@ Object {
|
||||
},
|
||||
"points": Object {
|
||||
"fill": [Function],
|
||||
"show": false,
|
||||
"size": [Function],
|
||||
"stroke": [Function],
|
||||
"width": [Function],
|
||||
},
|
||||
"x": false,
|
||||
"y": false,
|
||||
},
|
||||
"hooks": Object {
|
||||
"drawClear": Array [
|
||||
@ -454,9 +427,6 @@ Object {
|
||||
"init": Array [
|
||||
[Function],
|
||||
],
|
||||
"setCursor": Array [
|
||||
[Function],
|
||||
],
|
||||
},
|
||||
"scales": Object {
|
||||
"m/s": Object {
|
||||
@ -478,9 +448,7 @@ Object {
|
||||
"time": false,
|
||||
},
|
||||
},
|
||||
"select": Object {
|
||||
"show": false,
|
||||
},
|
||||
"select": undefined,
|
||||
"series": Array [
|
||||
Object {},
|
||||
Object {
|
||||
@ -504,7 +472,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`GraphNG utils preparePlotConfigBuilder stacking 2`] = `
|
||||
exports[`BarChart utils preparePlotConfigBuilder stacking 2`] = `
|
||||
Object {
|
||||
"axes": Array [
|
||||
Object {
|
||||
@ -565,13 +533,10 @@ Object {
|
||||
},
|
||||
"points": Object {
|
||||
"fill": [Function],
|
||||
"show": false,
|
||||
"size": [Function],
|
||||
"stroke": [Function],
|
||||
"width": [Function],
|
||||
},
|
||||
"x": false,
|
||||
"y": false,
|
||||
},
|
||||
"hooks": Object {
|
||||
"drawClear": Array [
|
||||
@ -580,9 +545,6 @@ Object {
|
||||
"init": Array [
|
||||
[Function],
|
||||
],
|
||||
"setCursor": Array [
|
||||
[Function],
|
||||
],
|
||||
},
|
||||
"scales": Object {
|
||||
"m/s": Object {
|
||||
@ -604,9 +566,7 @@ Object {
|
||||
"time": false,
|
||||
},
|
||||
},
|
||||
"select": Object {
|
||||
"show": false,
|
||||
},
|
||||
"select": undefined,
|
||||
"series": Array [
|
||||
Object {},
|
||||
Object {
|
||||
@ -630,7 +590,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`GraphNG utils preparePlotConfigBuilder stacking 3`] = `
|
||||
exports[`BarChart utils preparePlotConfigBuilder stacking 3`] = `
|
||||
Object {
|
||||
"axes": Array [
|
||||
Object {
|
||||
@ -691,13 +651,10 @@ Object {
|
||||
},
|
||||
"points": Object {
|
||||
"fill": [Function],
|
||||
"show": false,
|
||||
"size": [Function],
|
||||
"stroke": [Function],
|
||||
"width": [Function],
|
||||
},
|
||||
"x": false,
|
||||
"y": false,
|
||||
},
|
||||
"hooks": Object {
|
||||
"drawClear": Array [
|
||||
@ -706,9 +663,6 @@ Object {
|
||||
"init": Array [
|
||||
[Function],
|
||||
],
|
||||
"setCursor": Array [
|
||||
[Function],
|
||||
],
|
||||
},
|
||||
"scales": Object {
|
||||
"m/s": Object {
|
||||
@ -730,9 +684,7 @@ Object {
|
||||
"time": false,
|
||||
},
|
||||
},
|
||||
"select": Object {
|
||||
"show": false,
|
||||
},
|
||||
"select": undefined,
|
||||
"series": Array [
|
||||
Object {},
|
||||
Object {
|
||||
@ -756,7 +708,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`GraphNG utils preparePlotConfigBuilder value visibility 1`] = `
|
||||
exports[`BarChart utils preparePlotConfigBuilder value visibility 1`] = `
|
||||
Object {
|
||||
"axes": Array [
|
||||
Object {
|
||||
@ -817,13 +769,10 @@ Object {
|
||||
},
|
||||
"points": Object {
|
||||
"fill": [Function],
|
||||
"show": false,
|
||||
"size": [Function],
|
||||
"stroke": [Function],
|
||||
"width": [Function],
|
||||
},
|
||||
"x": false,
|
||||
"y": false,
|
||||
},
|
||||
"hooks": Object {
|
||||
"drawClear": Array [
|
||||
@ -832,9 +781,6 @@ Object {
|
||||
"init": Array [
|
||||
[Function],
|
||||
],
|
||||
"setCursor": Array [
|
||||
[Function],
|
||||
],
|
||||
},
|
||||
"scales": Object {
|
||||
"m/s": Object {
|
||||
@ -856,9 +802,7 @@ Object {
|
||||
"time": false,
|
||||
},
|
||||
},
|
||||
"select": Object {
|
||||
"show": false,
|
||||
},
|
||||
"select": undefined,
|
||||
"series": Array [
|
||||
Object {},
|
||||
Object {
|
||||
@ -882,7 +826,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`GraphNG utils preparePlotConfigBuilder value visibility 2`] = `
|
||||
exports[`BarChart utils preparePlotConfigBuilder value visibility 2`] = `
|
||||
Object {
|
||||
"axes": Array [
|
||||
Object {
|
||||
@ -943,13 +887,10 @@ Object {
|
||||
},
|
||||
"points": Object {
|
||||
"fill": [Function],
|
||||
"show": false,
|
||||
"size": [Function],
|
||||
"stroke": [Function],
|
||||
"width": [Function],
|
||||
},
|
||||
"x": false,
|
||||
"y": false,
|
||||
},
|
||||
"hooks": Object {
|
||||
"drawClear": Array [
|
||||
@ -958,9 +899,6 @@ Object {
|
||||
"init": Array [
|
||||
[Function],
|
||||
],
|
||||
"setCursor": Array [
|
||||
[Function],
|
||||
],
|
||||
},
|
||||
"scales": Object {
|
||||
"m/s": Object {
|
||||
@ -982,9 +920,7 @@ Object {
|
||||
"time": false,
|
||||
},
|
||||
},
|
||||
"select": Object {
|
||||
"show": false,
|
||||
},
|
||||
"select": undefined,
|
||||
"series": Array [
|
||||
Object {},
|
||||
Object {
|
||||
|
@ -1,13 +1,12 @@
|
||||
import uPlot, { Axis, Series, Cursor, Select } from 'uplot';
|
||||
import uPlot, { Axis, Series } from 'uplot';
|
||||
import { Quadtree, Rect, pointWithin } from './quadtree';
|
||||
import { distribute, SPACE_BETWEEN } from './distribute';
|
||||
|
||||
const pxRatio = devicePixelRatio;
|
||||
import { TooltipInterpolator } from '../uPlot/types';
|
||||
import { ScaleDirection, ScaleOrientation } from '../uPlot/config';
|
||||
|
||||
const groupDistr = SPACE_BETWEEN;
|
||||
const barDistr = SPACE_BETWEEN;
|
||||
|
||||
const font = Math.round(10 * pxRatio) + 'px Arial';
|
||||
const font = Math.round(10 * devicePixelRatio) + 'px Arial';
|
||||
|
||||
type WalkTwoCb = null | ((idx: number, offPx: number, dimPx: number) => void);
|
||||
|
||||
@ -41,8 +40,8 @@ function walkTwo(
|
||||
* @internal
|
||||
*/
|
||||
export interface BarsOptions {
|
||||
xOri: 1 | 0;
|
||||
xDir: 1 | -1;
|
||||
xOri: ScaleOrientation;
|
||||
xDir: ScaleDirection;
|
||||
groupWidth: number;
|
||||
barWidth: number;
|
||||
formatValue?: (seriesIdx: number, value: any) => string;
|
||||
@ -54,11 +53,11 @@ export interface BarsOptions {
|
||||
* @internal
|
||||
*/
|
||||
export function getConfig(opts: BarsOptions) {
|
||||
const { xOri: ori, xDir: dir, groupWidth, barWidth, formatValue, onHover, onLeave } = opts;
|
||||
const { xOri: ori, xDir: dir, groupWidth, barWidth, formatValue } = opts;
|
||||
|
||||
let qt: Quadtree;
|
||||
|
||||
const drawBars: Series.PathBuilder = (u, sidx, i0, i1) => {
|
||||
const drawBars: Series.PathBuilder = (u, sidx) => {
|
||||
return uPlot.orient(
|
||||
u,
|
||||
sidx,
|
||||
@ -112,29 +111,14 @@ export function getConfig(opts: BarsOptions) {
|
||||
const drawPoints: Series.Points.Show =
|
||||
formatValue == null
|
||||
? false
|
||||
: (u, sidx, i0, i1) => {
|
||||
: (u, sidx) => {
|
||||
u.ctx.font = font;
|
||||
u.ctx.fillStyle = 'white';
|
||||
|
||||
uPlot.orient(
|
||||
u,
|
||||
sidx,
|
||||
(
|
||||
series,
|
||||
dataX,
|
||||
dataY,
|
||||
scaleX,
|
||||
scaleY,
|
||||
valToPosX,
|
||||
valToPosY,
|
||||
xOff,
|
||||
yOff,
|
||||
xDim,
|
||||
yDim,
|
||||
moveTo,
|
||||
lineTo,
|
||||
rect
|
||||
) => {
|
||||
(series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
|
||||
let numGroups = dataX.length;
|
||||
let barsPerGroup = u.series.length - 1;
|
||||
|
||||
@ -148,13 +132,11 @@ export function getConfig(opts: BarsOptions) {
|
||||
if (dataY[ix] != null) {
|
||||
let yPos = valToPosY(dataY[ix]!, scaleY, yDim, yOff);
|
||||
|
||||
/* eslint-disable no-multi-spaces */
|
||||
let x = ori === 0 ? Math.round(lft + barWid / 2) : Math.round(yPos);
|
||||
let y = ori === 0 ? Math.round(yPos) : Math.round(lft + barWid / 2);
|
||||
|
||||
u.ctx.textAlign = ori === 0 ? 'center' : dataY[ix]! >= 0 ? 'left' : 'right';
|
||||
u.ctx.textBaseline = ori === 1 ? 'middle' : dataY[ix]! >= 0 ? 'bottom' : 'top';
|
||||
/* eslint-enable */
|
||||
|
||||
u.ctx.fillText(formatValue(sidx, dataY[ix]), x, y);
|
||||
}
|
||||
@ -165,23 +147,15 @@ export function getConfig(opts: BarsOptions) {
|
||||
return false;
|
||||
};
|
||||
|
||||
/*
|
||||
const yRange: Scale.Range = (u, dataMin, dataMax) => {
|
||||
// @ts-ignore
|
||||
let [min, max] = uPlot.rangeNum(0, dataMax, 0.05, true);
|
||||
return [0, max];
|
||||
};
|
||||
*/
|
||||
|
||||
const xSplits: Axis.Splits = (u: uPlot, axisIdx: number) => {
|
||||
const xSplits: Axis.Splits = (u: uPlot) => {
|
||||
const dim = ori === 0 ? u.bbox.width : u.bbox.height;
|
||||
const _dir = dir * (ori === 0 ? 1 : -1);
|
||||
|
||||
let splits: number[] = [];
|
||||
|
||||
distribute(u.data[0].length, groupWidth, groupDistr, null, (di, lftPct, widPct) => {
|
||||
let groupLftPx = (dim * lftPct) / pxRatio;
|
||||
let groupWidPx = (dim * widPct) / pxRatio;
|
||||
let groupLftPx = (dim * lftPct) / devicePixelRatio;
|
||||
let groupWidPx = (dim * widPct) / devicePixelRatio;
|
||||
|
||||
let groupCenterPx = groupLftPx + groupWidPx / 2;
|
||||
|
||||
@ -191,7 +165,6 @@ export function getConfig(opts: BarsOptions) {
|
||||
return _dir === 1 ? splits : splits.reverse();
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
const xValues: Axis.Values = (u) => u.data[0];
|
||||
|
||||
let hovered: Rect | null = null;
|
||||
@ -201,21 +174,6 @@ export function getConfig(opts: BarsOptions) {
|
||||
barMark.style.position = 'absolute';
|
||||
barMark.style.background = 'rgba(255,255,255,0.4)';
|
||||
|
||||
// hide crosshair cursor & hover points
|
||||
const cursor: Cursor = {
|
||||
x: false,
|
||||
y: false,
|
||||
points: {
|
||||
show: false,
|
||||
},
|
||||
};
|
||||
|
||||
// disable selection
|
||||
// uPlot types do not export the Select interface prior to 1.6.4
|
||||
const select: Partial<Select> = {
|
||||
show: false,
|
||||
};
|
||||
|
||||
const init = (u: uPlot) => {
|
||||
let over = u.root.querySelector('.u-over')! as HTMLElement;
|
||||
over.style.overflow = 'hidden';
|
||||
@ -235,10 +193,15 @@ export function getConfig(opts: BarsOptions) {
|
||||
};
|
||||
|
||||
// handle hover interaction with quadtree probing
|
||||
const setCursor = (u: uPlot) => {
|
||||
const interpolateBarChartTooltip: TooltipInterpolator = (
|
||||
updateActiveSeriesIdx,
|
||||
updateActiveDatapointIdx,
|
||||
updateTooltipPosition
|
||||
) => {
|
||||
return (u: uPlot) => {
|
||||
let found: Rect | null = null;
|
||||
let cx = u.cursor.left! * pxRatio;
|
||||
let cy = u.cursor.top! * pxRatio;
|
||||
let cx = u.cursor.left! * devicePixelRatio;
|
||||
let cy = u.cursor.top! * devicePixelRatio;
|
||||
|
||||
qt.get(cx, cy, 1, 1, (o) => {
|
||||
if (pointWithin(cx, cy, o.x, o.y, o.x + o.w, o.y + o.h)) {
|
||||
@ -249,36 +212,30 @@ export function getConfig(opts: BarsOptions) {
|
||||
if (found) {
|
||||
// prettier-ignore
|
||||
if (found !== hovered) {
|
||||
/* eslint-disable no-multi-spaces */
|
||||
barMark.style.display = '';
|
||||
barMark.style.left = found!.x / pxRatio + 'px';
|
||||
barMark.style.top = found!.y / pxRatio + 'px';
|
||||
barMark.style.width = found!.w / pxRatio + 'px';
|
||||
barMark.style.height = found!.h / pxRatio + 'px';
|
||||
barMark.style.left = found!.x / devicePixelRatio + 'px';
|
||||
barMark.style.top = found!.y / devicePixelRatio + 'px';
|
||||
barMark.style.width = found!.w / devicePixelRatio + 'px';
|
||||
barMark.style.height = found!.h / devicePixelRatio + 'px';
|
||||
hovered = found;
|
||||
/* eslint-enable */
|
||||
|
||||
if (onHover != null) {
|
||||
onHover(hovered!.sidx, hovered!.didx);
|
||||
}
|
||||
updateActiveSeriesIdx(hovered!.sidx);
|
||||
updateActiveDatapointIdx(hovered!.didx);
|
||||
updateTooltipPosition();
|
||||
}
|
||||
} else if (hovered != null) {
|
||||
if (onLeave != null) {
|
||||
onLeave(hovered!.sidx, hovered!.didx);
|
||||
}
|
||||
|
||||
updateActiveSeriesIdx(hovered!.sidx);
|
||||
updateActiveDatapointIdx(hovered!.didx);
|
||||
updateTooltipPosition();
|
||||
hovered = null;
|
||||
barMark.style.display = 'none';
|
||||
} else {
|
||||
updateTooltipPosition(true);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
// cursor & select opts
|
||||
cursor,
|
||||
select,
|
||||
|
||||
// scale & axis opts
|
||||
// yRange,
|
||||
xValues,
|
||||
xSplits,
|
||||
|
||||
@ -289,6 +246,6 @@ export function getConfig(opts: BarsOptions) {
|
||||
// hooks
|
||||
init,
|
||||
drawClear,
|
||||
setCursor,
|
||||
interpolateBarChartTooltip,
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { VizOrientation } from '@grafana/data';
|
||||
import { AxisConfig, GraphGradientMode, HideableFieldConfig, StackingMode } from '../uPlot/config';
|
||||
import { VizLegendOptions } from '../VizLegend/models.gen';
|
||||
import { OptionsWithLegend, OptionsWithTooltip } from '../../options';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
@ -14,9 +14,8 @@ export enum BarValueVisibility {
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface BarChartOptions {
|
||||
export interface BarChartOptions extends OptionsWithLegend, OptionsWithTooltip {
|
||||
orientation: VizOrientation;
|
||||
legend: VizLegendOptions;
|
||||
stacking: StackingMode;
|
||||
showValue: BarValueVisibility;
|
||||
barWidth: number;
|
||||
|
@ -1,8 +1,18 @@
|
||||
import { preparePlotConfigBuilder, preparePlotFrame } from './utils';
|
||||
import { createTheme, FieldConfig, FieldType, MutableDataFrame, VizOrientation } from '@grafana/data';
|
||||
import {
|
||||
createTheme,
|
||||
DefaultTimeZone,
|
||||
EventBusSrv,
|
||||
FieldConfig,
|
||||
FieldType,
|
||||
getDefaultTimeRange,
|
||||
MutableDataFrame,
|
||||
VizOrientation,
|
||||
} from '@grafana/data';
|
||||
import { BarChartFieldConfig, BarChartOptions, BarValueVisibility } from './types';
|
||||
import { GraphGradientMode, StackingMode } from '../uPlot/config';
|
||||
import { LegendDisplayMode } from '../VizLegend/models.gen';
|
||||
import { TooltipDisplayMode } from '../VizTooltip';
|
||||
|
||||
function mockDataFrame() {
|
||||
const df1 = new MutableDataFrame({
|
||||
@ -59,7 +69,7 @@ jest.mock('@grafana/data', () => ({
|
||||
DefaultTimeZone: 'utc',
|
||||
}));
|
||||
|
||||
describe('GraphNG utils', () => {
|
||||
describe('BarChart utils', () => {
|
||||
describe('preparePlotConfigBuilder', () => {
|
||||
const frame = mockDataFrame();
|
||||
|
||||
@ -74,30 +84,48 @@ describe('GraphNG utils', () => {
|
||||
calcs: [],
|
||||
},
|
||||
stacking: StackingMode.None,
|
||||
tooltip: {
|
||||
mode: TooltipDisplayMode.None,
|
||||
},
|
||||
};
|
||||
|
||||
it.each([VizOrientation.Auto, VizOrientation.Horizontal, VizOrientation.Vertical])('orientation', (v) => {
|
||||
const result = preparePlotConfigBuilder(frame!, createTheme(), {
|
||||
const result = preparePlotConfigBuilder({
|
||||
...config,
|
||||
orientation: v,
|
||||
frame: frame!,
|
||||
theme: createTheme(),
|
||||
timeZone: DefaultTimeZone,
|
||||
getTimeRange: getDefaultTimeRange,
|
||||
eventBus: new EventBusSrv(),
|
||||
}).getConfig();
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it.each([BarValueVisibility.Always, BarValueVisibility.Auto])('value visibility', (v) => {
|
||||
expect(
|
||||
preparePlotConfigBuilder(frame!, createTheme(), {
|
||||
preparePlotConfigBuilder({
|
||||
...config,
|
||||
showValue: v,
|
||||
frame: frame!,
|
||||
theme: createTheme(),
|
||||
timeZone: DefaultTimeZone,
|
||||
getTimeRange: getDefaultTimeRange,
|
||||
eventBus: new EventBusSrv(),
|
||||
}).getConfig()
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it.each([StackingMode.None, StackingMode.Percent, StackingMode.Normal])('stacking', (v) => {
|
||||
expect(
|
||||
preparePlotConfigBuilder(frame!, createTheme(), {
|
||||
preparePlotConfigBuilder({
|
||||
...config,
|
||||
stacking: v,
|
||||
frame: frame!,
|
||||
theme: createTheme(),
|
||||
timeZone: DefaultTimeZone,
|
||||
getTimeRange: getDefaultTimeRange,
|
||||
eventBus: new EventBusSrv(),
|
||||
}).getConfig()
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
@ -6,7 +6,6 @@ import {
|
||||
getFieldColorModeForField,
|
||||
getFieldDisplayName,
|
||||
getFieldSeriesColor,
|
||||
GrafanaTheme2,
|
||||
MutableDataFrame,
|
||||
VizOrientation,
|
||||
} from '@grafana/data';
|
||||
@ -14,76 +13,79 @@ import { BarChartFieldConfig, BarChartOptions, BarValueVisibility, defaultBarCha
|
||||
import { AxisPlacement, ScaleDirection, ScaleOrientation } from '../uPlot/config';
|
||||
import { BarsOptions, getConfig } from './bars';
|
||||
import { FIXED_UNIT } from '../GraphNG/GraphNG';
|
||||
import { Select } from 'uplot';
|
||||
import { ScaleDistribution } from '../uPlot/models.gen';
|
||||
import { PrepConfigOpts } from '../GraphNG/utils';
|
||||
|
||||
type PrepConfig = (opts: PrepConfigOpts<BarChartOptions>) => UPlotConfigBuilder;
|
||||
|
||||
/** @alpha */
|
||||
export function preparePlotConfigBuilder(
|
||||
data: DataFrame,
|
||||
theme: GrafanaTheme2,
|
||||
{ orientation, showValue, groupWidth, barWidth }: BarChartOptions
|
||||
) {
|
||||
const builder = new UPlotConfigBuilder();
|
||||
|
||||
// bar orientation -> x scale orientation & direction
|
||||
let xOri = ScaleOrientation.Vertical;
|
||||
let xDir = ScaleDirection.Down;
|
||||
let yOri = ScaleOrientation.Horizontal;
|
||||
let yDir = ScaleDirection.Right;
|
||||
|
||||
function getBarCharScaleOrientation(orientation: VizOrientation) {
|
||||
if (orientation === VizOrientation.Vertical) {
|
||||
xOri = ScaleOrientation.Horizontal;
|
||||
xDir = ScaleDirection.Right;
|
||||
yOri = ScaleOrientation.Vertical;
|
||||
yDir = ScaleDirection.Up;
|
||||
return {
|
||||
xOri: ScaleOrientation.Horizontal,
|
||||
xDir: ScaleDirection.Right,
|
||||
yOri: ScaleOrientation.Vertical,
|
||||
yDir: ScaleDirection.Up,
|
||||
};
|
||||
}
|
||||
|
||||
const formatValue =
|
||||
showValue !== BarValueVisibility.Never
|
||||
? (seriesIdx: number, value: any) => formattedValueToString(data.fields[seriesIdx].display!(value))
|
||||
: undefined;
|
||||
return {
|
||||
xOri: ScaleOrientation.Vertical,
|
||||
xDir: ScaleDirection.Down,
|
||||
yOri: ScaleOrientation.Horizontal,
|
||||
yDir: ScaleDirection.Right,
|
||||
};
|
||||
}
|
||||
|
||||
export const preparePlotConfigBuilder: PrepConfig = ({
|
||||
frame,
|
||||
theme,
|
||||
orientation,
|
||||
showValue,
|
||||
groupWidth,
|
||||
barWidth,
|
||||
}) => {
|
||||
const builder = new UPlotConfigBuilder();
|
||||
const defaultValueFormatter = (seriesIdx: number, value: any) =>
|
||||
formattedValueToString(frame.fields[seriesIdx].display!(value));
|
||||
|
||||
// bar orientation -> x scale orientation & direction
|
||||
const vizOrientation = getBarCharScaleOrientation(orientation);
|
||||
|
||||
const formatValue = showValue !== BarValueVisibility.Never ? defaultValueFormatter : undefined;
|
||||
|
||||
// Use bar width when only one field
|
||||
if (data.fields.length === 2) {
|
||||
if (frame.fields.length === 2) {
|
||||
groupWidth = barWidth;
|
||||
barWidth = 1;
|
||||
}
|
||||
|
||||
const opts: BarsOptions = {
|
||||
xOri,
|
||||
xDir,
|
||||
xOri: vizOrientation.xOri,
|
||||
xDir: vizOrientation.xDir,
|
||||
groupWidth,
|
||||
barWidth,
|
||||
formatValue,
|
||||
onHover: (seriesIdx: number, valueIdx: number) => {
|
||||
console.log('hover', { seriesIdx, valueIdx });
|
||||
},
|
||||
onLeave: (seriesIdx: number, valueIdx: number) => {
|
||||
console.log('leave', { seriesIdx, valueIdx });
|
||||
},
|
||||
};
|
||||
|
||||
const config = getConfig(opts);
|
||||
|
||||
builder.addHook('init', config.init);
|
||||
builder.addHook('drawClear', config.drawClear);
|
||||
builder.addHook('setCursor', config.setCursor);
|
||||
|
||||
builder.setCursor(config.cursor);
|
||||
builder.setSelect(config.select as Select);
|
||||
builder.setTooltipInterpolator(config.interpolateBarChartTooltip);
|
||||
|
||||
builder.addScale({
|
||||
scaleKey: 'x',
|
||||
isTime: false,
|
||||
distribution: ScaleDistribution.Ordinal,
|
||||
orientation: xOri,
|
||||
direction: xDir,
|
||||
orientation: vizOrientation.xOri,
|
||||
direction: vizOrientation.xDir,
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'x',
|
||||
isTime: false,
|
||||
placement: xOri === 0 ? AxisPlacement.Bottom : AxisPlacement.Left,
|
||||
placement: vizOrientation.xOri === 0 ? AxisPlacement.Bottom : AxisPlacement.Left,
|
||||
splits: config.xSplits,
|
||||
values: config.xValues,
|
||||
grid: false,
|
||||
@ -95,8 +97,8 @@ export function preparePlotConfigBuilder(
|
||||
let seriesIndex = 0;
|
||||
|
||||
// iterate the y values
|
||||
for (let i = 1; i < data.fields.length; i++) {
|
||||
const field = data.fields[i];
|
||||
for (let i = 1; i < frame.fields.length; i++) {
|
||||
const field = frame.fields[i];
|
||||
|
||||
field.state!.seriesIndex = seriesIndex++;
|
||||
|
||||
@ -127,7 +129,7 @@ export function preparePlotConfigBuilder(
|
||||
fieldIndex: i,
|
||||
frameIndex: 0,
|
||||
},
|
||||
fieldName: getFieldDisplayName(field, data),
|
||||
fieldName: getFieldDisplayName(field, frame),
|
||||
hideInLegend: customConfig.hideFrom?.legend,
|
||||
});
|
||||
|
||||
@ -138,8 +140,8 @@ export function preparePlotConfigBuilder(
|
||||
max: field.config.max,
|
||||
softMin: customConfig.axisSoftMin,
|
||||
softMax: customConfig.axisSoftMax,
|
||||
orientation: yOri,
|
||||
direction: yDir,
|
||||
orientation: vizOrientation.yOri,
|
||||
direction: vizOrientation.yDir,
|
||||
});
|
||||
|
||||
if (customConfig.axisPlacement !== AxisPlacement.Hidden) {
|
||||
@ -147,7 +149,7 @@ export function preparePlotConfigBuilder(
|
||||
if (!placement || placement === AxisPlacement.Auto) {
|
||||
placement = AxisPlacement.Left;
|
||||
}
|
||||
if (xOri === 1) {
|
||||
if (vizOrientation.xOri === 1) {
|
||||
if (placement === AxisPlacement.Left) {
|
||||
placement = AxisPlacement.Bottom;
|
||||
}
|
||||
@ -168,7 +170,7 @@ export function preparePlotConfigBuilder(
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export function preparePlotFrame(data: DataFrame[]) {
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
TimeRange,
|
||||
TimeZone,
|
||||
} from '@grafana/data';
|
||||
import { preparePlotFrame } from './utils';
|
||||
import { preparePlotFrame as defaultPreparePlotFrame } from './utils';
|
||||
|
||||
import { VizLegendOptions } from '../VizLegend/models.gen';
|
||||
import { PanelContext, PanelContextRoot } from '../PanelChrome/PanelContext';
|
||||
@ -38,9 +38,9 @@ export interface GraphNGProps extends Themeable2 {
|
||||
fields?: XYFieldMatchers; // default will assume timeseries data
|
||||
onLegendClick?: (event: GraphNGLegendEvent) => void;
|
||||
children?: (builder: UPlotConfigBuilder, alignedFrame: DataFrame) => React.ReactNode;
|
||||
|
||||
prepConfig: (alignedFrame: DataFrame, getTimeRange: () => TimeRange) => UPlotConfigBuilder;
|
||||
propsToDiff?: string[];
|
||||
preparePlotFrame?: (frames: DataFrame[], dimFields: XYFieldMatchers) => DataFrame;
|
||||
renderLegend: (config: UPlotConfigBuilder) => React.ReactElement;
|
||||
}
|
||||
|
||||
@ -84,9 +84,11 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
||||
prepState(props: GraphNGProps, withConfig = true) {
|
||||
let state: GraphNGState = null as any;
|
||||
|
||||
const { frames, fields } = props;
|
||||
const { frames, fields, preparePlotFrame } = props;
|
||||
|
||||
const alignedFrame = preparePlotFrame(
|
||||
const preparePlotFrameFn = preparePlotFrame || defaultPreparePlotFrame;
|
||||
|
||||
const alignedFrame = preparePlotFrameFn(
|
||||
frames,
|
||||
fields || {
|
||||
x: fieldMatchers.get(FieldMatcherID.firstTimeField).get({}),
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PlotConfig } from '../types';
|
||||
import { PlotConfig, TooltipInterpolator } from '../types';
|
||||
import { ScaleProps, UPlotScaleBuilder } from './UPlotScaleBuilder';
|
||||
import { SeriesProps, UPlotSeriesBuilder } from './UPlotSeriesBuilder';
|
||||
import { AxisProps, UPlotAxisBuilder } from './UPlotAxisBuilder';
|
||||
@ -23,6 +23,11 @@ export class UPlotConfigBuilder {
|
||||
private tz: string | undefined = undefined;
|
||||
// to prevent more than one threshold per scale
|
||||
private thresholds: Record<string, UPlotThresholdOptions> = {};
|
||||
/**
|
||||
* Custom handler for closest datapoint and series lookup. Technicaly returns uPlots setCursor hook
|
||||
* that sets tooltips state.
|
||||
*/
|
||||
tooltipInterpolator: TooltipInterpolator | undefined = undefined;
|
||||
|
||||
constructor(timeZone: TimeZone = DefaultTimeZone) {
|
||||
this.tz = getTimeZoneInfo(timeZone, Date.now())?.ianaName;
|
||||
@ -116,6 +121,10 @@ export class UPlotConfigBuilder {
|
||||
this.bands.push(band);
|
||||
}
|
||||
|
||||
setTooltipInterpolator(interpolator: TooltipInterpolator) {
|
||||
this.tooltipInterpolator = interpolator;
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
const config: PlotConfig = { series: [{}] };
|
||||
config.axes = this.ensureNonOverlappingAxes(Object.values(this.axes)).map((a) => a.getConfig());
|
||||
|
@ -38,7 +38,6 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
const plotCtx = usePlotContext();
|
||||
const [focusedSeriesIdx, setFocusedSeriesIdx] = useState<number | null>(null);
|
||||
const [focusedPointIdx, setFocusedPointIdx] = useState<number | null>(null);
|
||||
|
||||
const [coords, setCoords] = useState<CartesianCoords2D | null>(null);
|
||||
|
||||
const pluginId = `TooltipPlugin`;
|
||||
@ -50,6 +49,28 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
|
||||
// Add uPlot hooks to the config, or re-add when the config changed
|
||||
useLayoutEffect(() => {
|
||||
if (config.tooltipInterpolator) {
|
||||
// Custom toolitp positioning
|
||||
config.addHook('setCursor', (u) => {
|
||||
config.tooltipInterpolator!(setFocusedSeriesIdx, setFocusedPointIdx, (clear) => {
|
||||
if (clear) {
|
||||
setCoords(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const bbox = plotCtx.getCanvasBoundingBox();
|
||||
if (!bbox) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { x, y } = positionTooltip(u, bbox);
|
||||
if (x !== undefined && y !== undefined) {
|
||||
setCoords({ x, y });
|
||||
}
|
||||
})(u);
|
||||
});
|
||||
} else {
|
||||
// default series/datapoint idx retireval
|
||||
config.addHook('setCursor', (u) => {
|
||||
const bbox = plotCtx.getCanvasBoundingBox();
|
||||
if (!bbox) {
|
||||
@ -70,7 +91,8 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
config.addHook('setSeries', (_, idx) => {
|
||||
setFocusedSeriesIdx(idx);
|
||||
});
|
||||
}, [plotCtx, config]);
|
||||
}
|
||||
}, [plotCtx, config, setFocusedPointIdx, setFocusedSeriesIdx, setCoords]);
|
||||
|
||||
const plotInstance = plotCtx.plot;
|
||||
if (!plotInstance || focusedPointIdx === null) {
|
||||
|
@ -27,3 +27,9 @@ export abstract class PlotConfigBuilder<P, T> {
|
||||
constructor(public props: P) {}
|
||||
abstract getConfig(): T;
|
||||
}
|
||||
|
||||
export type TooltipInterpolator = (
|
||||
updateActiveSeriesIdx: (sIdx: number | null) => void,
|
||||
updateActiveDatapointIdx: (dIdx: number | null) => void,
|
||||
updateTooltipPosition: (clear?: boolean) => void
|
||||
) => (u: uPlot) => void;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { FieldType, PanelProps, VizOrientation } from '@grafana/data';
|
||||
import { BarChart, BarChartOptions, GraphNGLegendEvent } from '@grafana/ui';
|
||||
import { FieldType, PanelProps, TimeRange, VizOrientation } from '@grafana/data';
|
||||
import { BarChart, BarChartOptions, GraphNGLegendEvent, TooltipPlugin } from '@grafana/ui';
|
||||
import { hideSeriesConfigFactory } from '../timeseries/overrides/hideSeriesConfigFactory';
|
||||
|
||||
interface Props extends PanelProps<BarChartOptions> {}
|
||||
@ -14,6 +14,7 @@ export const BarChartPanel: React.FunctionComponent<Props> = ({
|
||||
width,
|
||||
height,
|
||||
fieldConfig,
|
||||
timeZone,
|
||||
onFieldConfigChange,
|
||||
}) => {
|
||||
const orientation = useMemo(() => {
|
||||
@ -57,13 +58,19 @@ export const BarChartPanel: React.FunctionComponent<Props> = ({
|
||||
|
||||
return (
|
||||
<BarChart
|
||||
data={data.series}
|
||||
frames={data.series}
|
||||
timeZone={timeZone}
|
||||
timeRange={({ from: 1, to: 1 } as unknown) as TimeRange} // HACK
|
||||
structureRev={data.structureRev}
|
||||
width={width}
|
||||
height={height}
|
||||
onLegendClick={onLegendClick}
|
||||
{...options}
|
||||
orientation={orientation}
|
||||
/>
|
||||
>
|
||||
{(config, alignedFrame) => {
|
||||
return <TooltipPlugin data={alignedFrame} config={config} mode={options.tooltip.mode} timeZone={timeZone} />;
|
||||
}}
|
||||
</BarChart>
|
||||
);
|
||||
};
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
graphFieldOptions,
|
||||
commonOptionsBuilder,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import { defaultBarChartFieldConfig } from '@grafana/ui/src/components/BarChart/types';
|
||||
|
||||
export const plugin = new PanelPlugin<BarChartOptions, BarChartFieldConfig>(BarChartPanel)
|
||||
@ -119,6 +120,7 @@ export const plugin = new PanelPlugin<BarChartOptions, BarChartFieldConfig>(BarC
|
||||
},
|
||||
});
|
||||
|
||||
commonOptionsBuilder.addTooltipOptions(builder);
|
||||
commonOptionsBuilder.addLegendOptions(builder);
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user