diff --git a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx index 5320f371f19..40dfda10f25 100755 --- a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx +++ b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx @@ -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 = ({ 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, diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index fcfe9538e80..b13b25008a0 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -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'; diff --git a/packages/grafana-ui/src/components/uPlot/config.ts b/packages/grafana-ui/src/components/uPlot/config.ts index 5b0518c0f07..f7825522ff2 100644 --- a/packages/grafana-ui/src/components/uPlot/config.ts +++ b/packages/grafana-ui/src/components/uPlot/config.ts @@ -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; } /** diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts index 18a25476b6a..befed7d3a0e 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts @@ -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', () => { diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotScaleBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotScaleBuilder.ts index d23125b8f99..011fe0fadcb 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotScaleBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotScaleBuilder.ts @@ -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 { @@ -16,19 +19,38 @@ export class UPlotScaleBuilder extends PlotConfigBuilder { } // 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, }, }; } diff --git a/public/app/plugins/panel/graph3/ScaleDistributionEditor.tsx b/public/app/plugins/panel/graph3/ScaleDistributionEditor.tsx new file mode 100644 index 00000000000..93fd7de30f5 --- /dev/null +++ b/public/app/plugins/panel/graph3/ScaleDistributionEditor.tsx @@ -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> = [ + { + label: 'Linear', + value: ScaleDistribution.Linear, + }, + { + label: 'Logarithmic', + value: ScaleDistribution.Logarithmic, + }, +]; + +const LOG_DISTRIBUTION_OPTIONS: Array> = [ + { + label: '2', + value: 2, + }, + { + label: '10', + value: 10, + }, +]; + +export const ScaleDistributionEditor: React.FC> = ({ + value, + onChange, +}) => { + return ( + + { + console.log(v, value); + onChange({ + ...value, + type: v!, + log: v === ScaleDistribution.Linear ? undefined : 2, + }); + }} + /> + {value.type === ScaleDistribution.Logarithmic && ( +