Commit 92527c26 by Dominik Prokop Committed by GitHub

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>
parent 3ee2e49d
......@@ -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', () => {
......
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,
},
};
}
......
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>
);
};
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,
});
},
})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment