mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
GraphNG: Enable scale distribution configuration (#29684)
* Enable scale distribution configuration * use uPlot's rangeLog() instead of rangeNum() for log scales * merge master * Remove unsupported log bases Co-authored-by: Leon Sorokin <leeoniya@gmail.com> Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
||||
import { alignDataFrames } from './utils';
|
||||
import { UPlotChart } from '../uPlot/Plot';
|
||||
import { PlotProps } from '../uPlot/types';
|
||||
import { AxisPlacement, GraphFieldConfig, DrawStyle, PointVisibility } from '../uPlot/config';
|
||||
import { AxisPlacement, DrawStyle, GraphFieldConfig, PointVisibility } from '../uPlot/config';
|
||||
import { useTheme } from '../../themes';
|
||||
import { VizLayout } from '../VizLayout/VizLayout';
|
||||
import { LegendDisplayMode, LegendItem, LegendOptions } from '../Legend/Legend';
|
||||
@@ -129,7 +129,14 @@ export const GraphNG: React.FC<GraphNGProps> = ({
|
||||
|
||||
if (customConfig.axisPlacement !== AxisPlacement.Hidden) {
|
||||
// The builder will manage unique scaleKeys and combine where appropriate
|
||||
builder.addScale({ scaleKey, min: field.config.min, max: field.config.max });
|
||||
builder.addScale({
|
||||
scaleKey,
|
||||
distribution: customConfig.scaleDistribution?.type,
|
||||
log: customConfig.scaleDistribution?.log,
|
||||
min: field.config.min,
|
||||
max: field.config.max,
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey,
|
||||
label: customConfig.axisLabel,
|
||||
|
||||
@@ -206,7 +206,7 @@ const LegacyForms = {
|
||||
export { LegacyForms, LegacyInputStatus };
|
||||
|
||||
// WIP, need renames and exports cleanup
|
||||
export { GraphFieldConfig, graphFieldOptions } from './uPlot/config';
|
||||
export * from './uPlot/config';
|
||||
export { UPlotChart } from './uPlot/Plot';
|
||||
export * from './uPlot/geometries';
|
||||
export * from './uPlot/plugins';
|
||||
|
||||
@@ -28,6 +28,11 @@ export enum LineInterpolation {
|
||||
StepAfter = 'stepAfter',
|
||||
}
|
||||
|
||||
export enum ScaleDistribution {
|
||||
Linear = 'linear',
|
||||
Logarithmic = 'log',
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
@@ -56,11 +61,16 @@ export interface PointsConfig {
|
||||
pointSymbol?: string; // eventually dot,star, etc
|
||||
}
|
||||
|
||||
export interface ScaleDistributionConfig {
|
||||
type: ScaleDistribution;
|
||||
log?: number;
|
||||
}
|
||||
// Axis is actually unique based on the unit... not each field!
|
||||
export interface AxisConfig {
|
||||
axisPlacement?: AxisPlacement;
|
||||
axisLabel?: string;
|
||||
axisWidth?: number; // pixels ideally auto?
|
||||
scaleDistribution?: ScaleDistributionConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { UPlotConfigBuilder } from './UPlotConfigBuilder';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { expect } from '../../../../../../public/test/lib/common';
|
||||
import { AxisPlacement, DrawStyle, PointVisibility } from '../config';
|
||||
import { AxisPlacement, DrawStyle, PointVisibility, ScaleDistribution } from '../config';
|
||||
|
||||
describe('UPlotConfigBuilder', () => {
|
||||
describe('scales config', () => {
|
||||
@@ -33,6 +33,8 @@ describe('UPlotConfigBuilder', () => {
|
||||
},
|
||||
"scale-y": Object {
|
||||
"auto": true,
|
||||
"distr": 1,
|
||||
"log": undefined,
|
||||
"range": [Function],
|
||||
"time": false,
|
||||
},
|
||||
@@ -57,6 +59,108 @@ describe('UPlotConfigBuilder', () => {
|
||||
|
||||
expect(Object.keys(builder.getConfig().scales!)).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('scale distribution', () => {
|
||||
it('allows linear scale configuration', () => {
|
||||
const builder = new UPlotConfigBuilder();
|
||||
|
||||
builder.addScale({
|
||||
scaleKey: 'scale-y',
|
||||
isTime: false,
|
||||
distribution: ScaleDistribution.Linear,
|
||||
});
|
||||
expect(builder.getConfig()).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"axes": Array [],
|
||||
"cursor": Object {
|
||||
"drag": Object {
|
||||
"setScale": false,
|
||||
},
|
||||
},
|
||||
"scales": Object {
|
||||
"scale-y": Object {
|
||||
"auto": true,
|
||||
"distr": 1,
|
||||
"log": undefined,
|
||||
"range": [Function],
|
||||
"time": false,
|
||||
},
|
||||
},
|
||||
"series": Array [
|
||||
Object {},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
describe('logarithmic scale', () => {
|
||||
it('defaults to log2', () => {
|
||||
const builder = new UPlotConfigBuilder();
|
||||
|
||||
builder.addScale({
|
||||
scaleKey: 'scale-y',
|
||||
isTime: false,
|
||||
distribution: ScaleDistribution.Linear,
|
||||
});
|
||||
|
||||
expect(builder.getConfig()).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"axes": Array [],
|
||||
"cursor": Object {
|
||||
"drag": Object {
|
||||
"setScale": false,
|
||||
},
|
||||
},
|
||||
"scales": Object {
|
||||
"scale-y": Object {
|
||||
"auto": true,
|
||||
"distr": 1,
|
||||
"log": undefined,
|
||||
"range": [Function],
|
||||
"time": false,
|
||||
},
|
||||
},
|
||||
"series": Array [
|
||||
Object {},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('allows custom log configuration', () => {
|
||||
const builder = new UPlotConfigBuilder();
|
||||
|
||||
builder.addScale({
|
||||
scaleKey: 'scale-y',
|
||||
isTime: false,
|
||||
distribution: ScaleDistribution.Linear,
|
||||
log: 10,
|
||||
});
|
||||
|
||||
expect(builder.getConfig()).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"axes": Array [],
|
||||
"cursor": Object {
|
||||
"drag": Object {
|
||||
"setScale": false,
|
||||
},
|
||||
},
|
||||
"scales": Object {
|
||||
"scale-y": Object {
|
||||
"auto": true,
|
||||
"distr": 1,
|
||||
"log": undefined,
|
||||
"range": [Function],
|
||||
"time": false,
|
||||
},
|
||||
},
|
||||
"series": Array [
|
||||
Object {},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('allows axes configuration', () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import uPlot, { Scale } from 'uplot';
|
||||
import { PlotConfigBuilder } from '../types';
|
||||
import { ScaleDistribution } from '../config';
|
||||
|
||||
export interface ScaleProps {
|
||||
scaleKey: string;
|
||||
@@ -7,6 +8,8 @@ export interface ScaleProps {
|
||||
min?: number | null;
|
||||
max?: number | null;
|
||||
range?: () => number[]; // min/max
|
||||
distribution?: ScaleDistribution;
|
||||
log?: number;
|
||||
}
|
||||
|
||||
export class UPlotScaleBuilder extends PlotConfigBuilder<ScaleProps, Scale> {
|
||||
@@ -16,19 +19,38 @@ export class UPlotScaleBuilder extends PlotConfigBuilder<ScaleProps, Scale> {
|
||||
}
|
||||
|
||||
// uPlot range function
|
||||
range = (u: uPlot, dataMin: number, dataMax: number) => {
|
||||
range = (u: uPlot, dataMin: number, dataMax: number, scaleKey: string) => {
|
||||
const { min, max } = this.props;
|
||||
const [smin, smax] = uPlot.rangeNum(min ?? dataMin, max ?? dataMax, 0.1 as any, true);
|
||||
|
||||
const scale = u.scales[scaleKey];
|
||||
|
||||
let smin, smax;
|
||||
|
||||
if (scale.distr === 1) {
|
||||
[smin, smax] = uPlot.rangeNum(min ?? dataMin, max ?? dataMax, 0.1 as any, true);
|
||||
} else if (scale.distr === 3) {
|
||||
/**@ts-ignore (uPlot 1.4.7 typings are wrong and exclude logBase arg) */
|
||||
[smin, smax] = uPlot.rangeLog(min ?? dataMin, max ?? dataMax, scale.log, true);
|
||||
}
|
||||
|
||||
return [min ?? smin, max ?? smax];
|
||||
};
|
||||
|
||||
getConfig() {
|
||||
const { isTime, scaleKey, range } = this.props;
|
||||
const distribution = !isTime
|
||||
? {
|
||||
distr: this.props.distribution === ScaleDistribution.Logarithmic ? 3 : 1,
|
||||
log: this.props.distribution === ScaleDistribution.Logarithmic ? this.props.log || 2 : undefined,
|
||||
}
|
||||
: {};
|
||||
|
||||
return {
|
||||
[scaleKey]: {
|
||||
time: isTime,
|
||||
auto: !isTime,
|
||||
range: range ?? this.range,
|
||||
...distribution,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
63
public/app/plugins/panel/graph3/ScaleDistributionEditor.tsx
Normal file
63
public/app/plugins/panel/graph3/ScaleDistributionEditor.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { FieldOverrideEditorProps, SelectableValue } from '@grafana/data';
|
||||
import { HorizontalGroup, RadioButtonGroup, ScaleDistribution, ScaleDistributionConfig, Select } from '@grafana/ui';
|
||||
|
||||
const DISTRIBUTION_OPTIONS: Array<SelectableValue<ScaleDistribution>> = [
|
||||
{
|
||||
label: 'Linear',
|
||||
value: ScaleDistribution.Linear,
|
||||
},
|
||||
{
|
||||
label: 'Logarithmic',
|
||||
value: ScaleDistribution.Logarithmic,
|
||||
},
|
||||
];
|
||||
|
||||
const LOG_DISTRIBUTION_OPTIONS: Array<SelectableValue<number>> = [
|
||||
{
|
||||
label: '2',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '10',
|
||||
value: 10,
|
||||
},
|
||||
];
|
||||
|
||||
export const ScaleDistributionEditor: React.FC<FieldOverrideEditorProps<ScaleDistributionConfig, any>> = ({
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
return (
|
||||
<HorizontalGroup>
|
||||
<RadioButtonGroup
|
||||
value={value.type || ScaleDistribution.Linear}
|
||||
options={DISTRIBUTION_OPTIONS}
|
||||
onChange={v => {
|
||||
console.log(v, value);
|
||||
onChange({
|
||||
...value,
|
||||
type: v!,
|
||||
log: v === ScaleDistribution.Linear ? undefined : 2,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{value.type === ScaleDistribution.Logarithmic && (
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus
|
||||
options={LOG_DISTRIBUTION_OPTIONS}
|
||||
value={value.log || 2}
|
||||
prefix={'base'}
|
||||
width={12}
|
||||
onChange={v => {
|
||||
onChange({
|
||||
...value,
|
||||
log: v.value!,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
};
|
||||
@@ -1,15 +1,24 @@
|
||||
import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
||||
import { LegendDisplayMode } from '@grafana/ui';
|
||||
import {
|
||||
GraphFieldConfig,
|
||||
PointVisibility,
|
||||
DrawStyle,
|
||||
FieldColorModeId,
|
||||
FieldConfigProperty,
|
||||
FieldType,
|
||||
identityOverrideProcessor,
|
||||
PanelPlugin,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
AxisPlacement,
|
||||
DrawStyle,
|
||||
GraphFieldConfig,
|
||||
graphFieldOptions,
|
||||
} from '@grafana/ui/src/components/uPlot/config';
|
||||
LegendDisplayMode,
|
||||
PointVisibility,
|
||||
ScaleDistribution,
|
||||
ScaleDistributionConfig,
|
||||
} from '@grafana/ui';
|
||||
import { GraphPanel } from './GraphPanel';
|
||||
import { graphPanelChangedHandler } from './migrations';
|
||||
import { Options } from './types';
|
||||
import { ScaleDistributionEditor } from './ScaleDistributionEditor';
|
||||
|
||||
export const plugin = new PanelPlugin<Options, GraphFieldConfig>(GraphPanel)
|
||||
.setPanelChangeHandler(graphPanelChangedHandler)
|
||||
@@ -125,6 +134,17 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(GraphPanel)
|
||||
placeholder: 'Auto',
|
||||
},
|
||||
showIf: c => c.axisPlacement !== AxisPlacement.Hidden,
|
||||
})
|
||||
.addCustomEditor<void, ScaleDistributionConfig>({
|
||||
id: 'scaleDistribution',
|
||||
path: 'scaleDistribution',
|
||||
name: 'Scale',
|
||||
category: ['Axis'],
|
||||
editor: ScaleDistributionEditor,
|
||||
override: ScaleDistributionEditor,
|
||||
defaultValue: { type: ScaleDistribution.Linear },
|
||||
shouldApply: f => f.type === FieldType.number,
|
||||
process: identityOverrideProcessor,
|
||||
});
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user