From 8250b59dfec83e5fac5928f4d35156910e5838d8 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 16 Dec 2020 11:17:56 -0800 Subject: [PATCH] GraphNG: support fill gradient (#29765) --- .../panel-graph/graph-ng-gradient-area.json | 571 ++++++++++++++++++ .../src/components/GraphNG/GraphNG.tsx | 1 + .../src/components/Sparkline/Sparkline.tsx | 1 + .../grafana-ui/src/components/uPlot/Plot.tsx | 9 +- .../grafana-ui/src/components/uPlot/config.ts | 44 +- .../uPlot/config/UPlotConfigBuilder.test.ts | 55 +- .../uPlot/config/UPlotSeriesBuilder.ts | 106 +++- packages/grafana-ui/src/utils/measureText.ts | 25 +- .../__snapshots__/migrations.test.ts.snap | 3 +- public/app/plugins/panel/graph3/migrations.ts | 32 +- public/app/plugins/panel/graph3/module.tsx | 9 + 11 files changed, 818 insertions(+), 38 deletions(-) create mode 100644 devenv/dev-dashboards/panel-graph/graph-ng-gradient-area.json diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-gradient-area.json b/devenv/dev-dashboards/panel-graph/graph-ng-gradient-area.json new file mode 100644 index 00000000000..a05f30c4e40 --- /dev/null +++ b/devenv/dev-dashboards/panel-graph/graph-ng-gradient-area.json @@ -0,0 +1,571 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillGradient": 0.5, + "fillOpacity": 40, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 6, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "nullValueMode": "null", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "A-series" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "rgb(48, 139, 237)", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 10, + "x": 0, + "y": 0 + }, + "id": 2, + "interval": "1m", + "links": [], + "options": { + "graph": {}, + "legend": { + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "single" + } + }, + "pluginVersion": "7.4.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Req/s", + "type": "graph3" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "bars", + "fillGradient": 0.4, + "fillOpacity": 53, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 6, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "nullValueMode": "null", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "A-series" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 7, + "x": 10, + "y": 0 + }, + "id": 11, + "interval": "1m", + "links": [], + "maxDataPoints": 10, + "options": { + "graph": {}, + "legend": { + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "single" + } + }, + "pluginVersion": "7.4.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Req/s", + "type": "graph3" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "bars", + "fillGradient": "opacity", + "fillOpacity": 100, + "lineInterpolation": "linear", + "lineWidth": 0, + "pointSize": 6, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "nullValueMode": "null", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "A-series" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 7, + "x": 17, + "y": 0 + }, + "id": 12, + "interval": "1m", + "links": [], + "maxDataPoints": 20, + "options": { + "graph": {}, + "legend": { + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "single" + } + }, + "pluginVersion": "7.4.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Req/s", + "type": "graph3" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillGradient": 0.5, + "fillOpacity": 20, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 6, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "nullValueMode": "null", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decgbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "A-series" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 10, + "x": 0, + "y": 8 + }, + "id": 7, + "interval": "1m", + "links": [], + "options": { + "graph": {}, + "legend": { + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "single" + } + }, + "pluginVersion": "7.4.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Memory", + "type": "graph3" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillGradient": "hue", + "fillOpacity": 62, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 6, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "nullValueMode": "null", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "A-series" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 14, + "x": 10, + "y": 8 + }, + "id": 13, + "interval": "1m", + "links": [], + "maxDataPoints": 80, + "options": { + "graph": {}, + "legend": { + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "single" + } + }, + "pluginVersion": "7.4.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Hue gradient mode", + "type": "graph3" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "bars", + "fillGradient": "hue", + "fillOpacity": 100, + "lineInterpolation": "smooth", + "lineWidth": 0, + "pointSize": 6, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "nullValueMode": "null", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "A-series" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 11, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 16, + "interval": "1m", + "links": [], + "maxDataPoints": 50, + "options": { + "graph": {}, + "legend": { + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "single" + } + }, + "pluginVersion": "7.4.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "random_walk" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Hue gradient mode", + "type": "graph3" + } + ], + "schemaVersion": 27, + "style": "dark", + "tags": ["gdev", "panel-tests"], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + }, + "timezone": "", + "title": "Panel Tests - Graph NG - Gradient Area Fills", + "uid": "_gWgX2JGk", + "version": 1 +} diff --git a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx index a08cb4cc1e7..100891508ba 100755 --- a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx +++ b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx @@ -165,6 +165,7 @@ export const GraphNG: React.FC = ({ pointColor: customConfig.pointColor ?? seriesColor, fillOpacity: customConfig.fillOpacity, spanNulls: customConfig.spanNulls || false, + fillGradient: customConfig.fillGradient, }); if (hasLegend.current) { diff --git a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx index 0270d2bb3fb..a035956253d 100755 --- a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx +++ b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx @@ -133,6 +133,7 @@ export class Sparkline extends PureComponent { const colorMode = getFieldColorModeForField(field); const seriesColor = colorMode.getCalculator(field, theme)(0, 0); const pointsMode = customConfig.drawStyle === DrawStyle.Points ? PointVisibility.Always : customConfig.showPoints; + builder.addSeries({ scaleKey, drawStyle: customConfig.drawStyle!, diff --git a/packages/grafana-ui/src/components/uPlot/Plot.tsx b/packages/grafana-ui/src/components/uPlot/Plot.tsx index cf924042db7..505a2685c34 100755 --- a/packages/grafana-ui/src/components/uPlot/Plot.tsx +++ b/packages/grafana-ui/src/components/uPlot/Plot.tsx @@ -8,9 +8,12 @@ import { DataFrame } from '@grafana/data'; import { UPlotConfigBuilder } from './config/UPlotConfigBuilder'; import usePrevious from 'react-use/lib/usePrevious'; -// uPlot abstraction responsible for plot initialisation, setup and refresh -// Receives a data frame that is x-axis aligned, as of https://github.com/leeoniya/uPlot/tree/master/docs#data-format -// Exposes contexts for plugins registration and uPlot instance access +/** + * @internal + * uPlot abstraction responsible for plot initialisation, setup and refresh + * Receives a data frame that is x-axis aligned, as of https://github.com/leeoniya/uPlot/tree/master/docs#data-format + * Exposes contexts for plugins registration and uPlot instance access + */ export const UPlotChart: React.FC = props => { const canvasRef = useRef(null); const plotInstance = useRef(); diff --git a/packages/grafana-ui/src/components/uPlot/config.ts b/packages/grafana-ui/src/components/uPlot/config.ts index f7825522ff2..a7c74756a59 100644 --- a/packages/grafana-ui/src/components/uPlot/config.ts +++ b/packages/grafana-ui/src/components/uPlot/config.ts @@ -1,5 +1,8 @@ import { SelectableValue } from '@grafana/data'; +/** + * @alpha + */ export enum AxisPlacement { Auto = 'auto', // First axis on the left, the rest on the right Top = 'top', @@ -9,18 +12,27 @@ export enum AxisPlacement { Hidden = 'hidden', } +/** + * @alpha + */ export enum PointVisibility { Auto = 'auto', // will show points when the density is low or line is hidden Never = 'never', Always = 'always', } +/** + * @alpha + */ export enum DrawStyle { Line = 'line', // default Bars = 'bars', // will also have a gap percent Points = 'points', // Only show points } +/** + * @alpha + */ export enum LineInterpolation { Linear = 'linear', Smooth = 'smooth', @@ -28,6 +40,9 @@ export enum LineInterpolation { StepAfter = 'stepAfter', } +/** + * @alpha + */ export enum ScaleDistribution { Linear = 'linear', Logarithmic = 'log', @@ -40,6 +55,7 @@ export interface LineConfig { lineColor?: string; lineWidth?: number; lineInterpolation?: LineInterpolation; + lineDash?: number[]; spanNulls?: boolean; } @@ -49,6 +65,16 @@ export interface LineConfig { export interface AreaConfig { fillColor?: string; fillOpacity?: number; + fillGradient?: AreaGradientMode; +} + +/** + * @alpha + */ +export enum AreaGradientMode { + None = 'none', + Opacity = 'opacity', + Hue = 'hue', } /** @@ -61,11 +87,18 @@ export interface PointsConfig { pointSymbol?: string; // eventually dot,star, etc } +/** + * @alpha + */ export interface ScaleDistributionConfig { type: ScaleDistribution; log?: number; } -// Axis is actually unique based on the unit... not each field! + +/** + * @alpha + * Axis is actually unique based on the unit... not each field! + */ export interface AxisConfig { axisPlacement?: AxisPlacement; axisLabel?: string; @@ -80,6 +113,9 @@ export interface GraphFieldConfig extends LineConfig, AreaConfig, PointsConfig, drawStyle?: DrawStyle; } +/** + * @alpha + */ export const graphFieldOptions = { drawStyle: [ { label: 'Lines', value: DrawStyle.Line }, @@ -106,4 +142,10 @@ export const graphFieldOptions = { { label: 'Right', value: AxisPlacement.Right }, { label: 'Hidden', value: AxisPlacement.Hidden }, ] as Array>, + + fillGradient: [ + { label: 'None', value: undefined }, + { label: 'Opacity', value: AreaGradientMode.Opacity }, + { label: 'Hue', value: AreaGradientMode.Hue }, + ] as Array>, }; 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 8ed38a4e282..8d8382adb46 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, ScaleDistribution } from '../config'; +import { AreaGradientMode, AxisPlacement, DrawStyle, PointVisibility, ScaleDistribution } from '../config'; describe('UPlotConfigBuilder', () => { describe('scales config', () => { @@ -266,13 +266,62 @@ describe('UPlotConfigBuilder', () => { expect(builder.getAxisPlacement('y2')).toBe(AxisPlacement.Right); }); + it('When fillColor is not set fill', () => { + const builder = new UPlotConfigBuilder(); + builder.addSeries({ + drawStyle: DrawStyle.Line, + scaleKey: 'scale-x', + lineColor: '#0000ff', + }); + + expect(builder.getConfig().series[1].fill).toBe(undefined); + }); + + it('When fillOpacity is set', () => { + const builder = new UPlotConfigBuilder(); + builder.addSeries({ + drawStyle: DrawStyle.Line, + scaleKey: 'scale-x', + lineColor: '#FFAABB', + fillOpacity: 50, + }); + + expect(builder.getConfig().series[1].fill).toBe('rgba(255, 170, 187, 0.5)'); + }); + + it('When fillColor is set ignore fillOpacity', () => { + const builder = new UPlotConfigBuilder(); + builder.addSeries({ + drawStyle: DrawStyle.Line, + scaleKey: 'scale-x', + lineColor: '#FFAABB', + fillOpacity: 50, + fillColor: '#FF0000', + }); + + expect(builder.getConfig().series[1].fill).toBe('#FF0000'); + }); + + it('When fillGradient mode is opacity', () => { + const builder = new UPlotConfigBuilder(); + builder.addSeries({ + drawStyle: DrawStyle.Line, + scaleKey: 'scale-x', + lineColor: '#FFAABB', + fillOpacity: 50, + fillGradient: AreaGradientMode.Opacity, + }); + + expect(builder.getConfig().series[1].fill).toBeInstanceOf(Function); + }); + it('allows series configuration', () => { const builder = new UPlotConfigBuilder(); builder.addSeries({ drawStyle: DrawStyle.Line, scaleKey: 'scale-x', - fillColor: '#ff0000', fillOpacity: 50, + fillGradient: AreaGradientMode.Opacity, showPoints: PointVisibility.Auto, pointSize: 5, pointColor: '#00ff00', @@ -299,7 +348,7 @@ describe('UPlotConfigBuilder', () => { "series": Array [ Object {}, Object { - "fill": "rgba(255, 0, 0, 0.5)", + "fill": [Function], "paths": [Function], "points": Object { "fill": "#00ff00", diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts index 4c7744e95cb..48194755f20 100755 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts @@ -1,6 +1,15 @@ import tinycolor from 'tinycolor2'; import uPlot, { Series } from 'uplot'; -import { DrawStyle, LineConfig, AreaConfig, PointsConfig, PointVisibility, LineInterpolation } from '../config'; +import { getCanvasContext } from '../../../utils/measureText'; +import { + DrawStyle, + LineConfig, + AreaConfig, + PointsConfig, + PointVisibility, + LineInterpolation, + AreaGradientMode, +} from '../config'; import { PlotConfigBuilder } from '../types'; export interface SeriesProps extends LineConfig, AreaConfig, PointsConfig { @@ -18,8 +27,6 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder { showPoints, pointColor, pointSize, - fillColor, - fillOpacity, scaleKey, spanNulls, } = this.props; @@ -56,31 +63,41 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder { pointsConfig.points!.show = true; } - let fillConfig: any | undefined; - let fillOpacityNumber = fillOpacity ?? 0; - - if (fillColor) { - fillConfig = { - fill: fillColor, - }; - } - - if (fillOpacityNumber !== 0) { - fillConfig = { - fill: tinycolor(fillColor ?? lineColor) - .setAlpha(fillOpacityNumber / 100) - .toRgbString(), - }; - } - return { scale: scaleKey, spanGaps: spanNulls, + fill: this.getFill(), ...lineConfig, ...pointsConfig, - ...fillConfig, }; } + + getFill(): Series.Fill | undefined { + const { lineColor, fillColor, fillGradient, fillOpacity } = this.props; + + if (fillColor) { + return fillColor; + } + + const mode = fillGradient ?? AreaGradientMode.None; + let fillOpacityNumber = fillOpacity ?? 0; + + if (mode !== AreaGradientMode.None) { + return getCanvasGradient({ + color: (fillColor ?? lineColor)!, + opacity: fillOpacityNumber / 100, + mode, + }); + } + + if (fillOpacityNumber > 0) { + return tinycolor(lineColor) + .setAlpha(fillOpacityNumber / 100) + .toString(); + } + + return undefined; + } } interface PathBuilders { @@ -130,3 +147,50 @@ function mapDrawStyleToPathBuilder( return builders.linear; // the default } + +interface AreaGradientOptions { + color: string; + mode: AreaGradientMode; + opacity: number; +} + +function getCanvasGradient(opts: AreaGradientOptions): (self: uPlot, seriesIdx: number) => CanvasGradient { + return (plot: uPlot, seriesIdx: number) => { + const { color, mode, opacity } = opts; + + const ctx = getCanvasContext(); + const gradient = ctx.createLinearGradient(0, plot.bbox.top, 0, plot.bbox.top + plot.bbox.height); + + switch (mode) { + case AreaGradientMode.Hue: + const color1 = tinycolor(color) + .spin(-25) + .darken(30) + .setAlpha(opacity) + .toRgbString(); + const color2 = tinycolor(color) + .spin(25) + .lighten(35) + .setAlpha(opacity) + .toRgbString(); + gradient.addColorStop(0, color2); + gradient.addColorStop(1, color1); + + case AreaGradientMode.Opacity: + default: + gradient.addColorStop( + 0, + tinycolor(color) + .setAlpha(opacity) + .toRgbString() + ); + gradient.addColorStop( + 1, + tinycolor(color) + .setAlpha(0) + .toRgbString() + ); + return gradient; + } + }; +} diff --git a/packages/grafana-ui/src/utils/measureText.ts b/packages/grafana-ui/src/utils/measureText.ts index 2593d310e80..2b20a5c8ae6 100644 --- a/packages/grafana-ui/src/utils/measureText.ts +++ b/packages/grafana-ui/src/utils/measureText.ts @@ -1,6 +1,22 @@ let canvas: HTMLCanvasElement | null = null; const cache: Record = {}; +/** + * @internal + */ +export function getCanvasContext() { + if (canvas === null) { + canvas = document.createElement('canvas'); + } + + const context = canvas.getContext('2d'); + if (!context) { + throw new Error('Could not create context'); + } + + return context; +} + /** * @beta */ @@ -13,14 +29,7 @@ export function measureText(text: string, fontSize: number): TextMetrics { return fromCache; } - if (canvas === null) { - canvas = document.createElement('canvas'); - } - - const context = canvas.getContext('2d'); - if (!context) { - throw new Error('Could not create context'); - } + const context = getCanvasContext(); context.font = fontStyle; const metrics = context.measureText(text); diff --git a/public/app/plugins/panel/graph3/__snapshots__/migrations.test.ts.snap b/public/app/plugins/panel/graph3/__snapshots__/migrations.test.ts.snap index 8f1a2b8fc8c..92417a1ee82 100644 --- a/public/app/plugins/panel/graph3/__snapshots__/migrations.test.ts.snap +++ b/public/app/plugins/panel/graph3/__snapshots__/migrations.test.ts.snap @@ -33,7 +33,8 @@ Object { "custom": Object { "axisPlacement": "hidden", "drawStyle": "line", - "fillOpacity": 50, + "fillGradient": "opacity", + "fillOpacity": 60, "lineInterpolation": "stepAfter", "lineWidth": 1, "pointSize": 6, diff --git a/public/app/plugins/panel/graph3/migrations.ts b/public/app/plugins/panel/graph3/migrations.ts index ec664a604a4..a1697a75504 100644 --- a/public/app/plugins/panel/graph3/migrations.ts +++ b/public/app/plugins/panel/graph3/migrations.ts @@ -11,7 +11,13 @@ import { FieldColorModeId, } from '@grafana/data'; import { GraphFieldConfig, LegendDisplayMode } from '@grafana/ui'; -import { AxisPlacement, DrawStyle, LineInterpolation, PointVisibility } from '@grafana/ui/src/components/uPlot/config'; +import { + AreaGradientMode, + AxisPlacement, + DrawStyle, + LineInterpolation, + PointVisibility, +} from '@grafana/ui/src/components/uPlot/config'; import { Options } from './types'; import omitBy from 'lodash/omitBy'; import isNil from 'lodash/isNil'; @@ -112,6 +118,18 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour value: v * 10, // was 0-10, new graph is 0 - 100 }); break; + case 'fillGradient': + if (v) { + rule.properties.push({ + id: 'custom.fillGradient', + value: 'opacity', // was 0-10 + }); + rule.properties.push({ + id: 'custom.fillOpacity', + value: v * 10, // was 0-10, new graph is 0 - 100 + }); + } + break; case 'points': rule.properties.push({ id: 'custom.showPoints', @@ -159,6 +177,7 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour const graph = y1.custom ?? ({} as GraphFieldConfig); graph.drawStyle = angular.bars ? DrawStyle.Bars : angular.lines ? DrawStyle.Line : DrawStyle.Points; + if (angular.points) { graph.showPoints = PointVisibility.Always; } else if (graph.drawStyle !== DrawStyle.Points) { @@ -166,19 +185,30 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour } graph.lineWidth = angular.linewidth; + if (isNumber(angular.pointradius)) { graph.pointSize = 2 + angular.pointradius * 2; } + if (isNumber(angular.fill)) { graph.fillOpacity = angular.fill * 10; // fill was 0 - 10, new is 0 to 100 } + + if (isNumber(angular.fillGradient) && angular.fillGradient > 0) { + graph.fillGradient = AreaGradientMode.Opacity; + graph.fillOpacity = angular.fillGradient * 10; // fill is 0-10 + } + graph.spanNulls = angular.nullPointMode === NullValueMode.Null; + if (angular.steppedLine) { graph.lineInterpolation = LineInterpolation.StepAfter; } + if (graph.drawStyle === DrawStyle.Bars) { graph.fillOpacity = 1.0; // bars were always } + y1.custom = omitBy(graph, isNil); y1.nullValueMode = angular.nullPointMode as NullValueMode; diff --git a/public/app/plugins/panel/graph3/module.tsx b/public/app/plugins/panel/graph3/module.tsx index e046d420e89..c691afe7f35 100644 --- a/public/app/plugins/panel/graph3/module.tsx +++ b/public/app/plugins/panel/graph3/module.tsx @@ -74,6 +74,15 @@ export const plugin = new PanelPlugin(GraphPanel) }, showIf: c => c.drawStyle !== DrawStyle.Points, }) + .addRadio({ + path: 'fillGradient', + name: 'Fill gradient', + defaultValue: graphFieldOptions.fillGradient[0], + settings: { + options: graphFieldOptions.fillGradient, + }, + showIf: c => !!(c.drawStyle !== DrawStyle.Points && c.fillOpacity && c.fillOpacity > 0), + }) .addRadio({ path: 'spanNulls', name: 'Null values',