From 1f65d8a8995188b3795b692fc85a3a7a5ddc1f92 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 17 Jun 2021 17:08:54 -0400 Subject: [PATCH] Time series: always show single points if surrounded by nulls (#35845) (#35909) (cherry picked from commit 503c3b8f66343f4bc49e0985081c810764f18b66) Co-authored-by: Leon Sorokin --- .../panel-graph/graph-ng-nulls.json | 299 ++++++++++++------ .../GraphNG/__snapshots__/utils.test.ts.snap | 5 + .../src/components/TimeSeries/utils.ts | 39 +++ .../uPlot/config/UPlotConfigBuilder.test.ts | 4 + .../uPlot/config/UPlotSeriesBuilder.ts | 3 + .../barchart/__snapshots__/utils.test.ts.snap | 8 + 6 files changed, 270 insertions(+), 88 deletions(-) diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-nulls.json b/devenv/dev-dashboards/panel-graph/graph-ng-nulls.json index 95c10fac68b..2c86c73b870 100644 --- a/devenv/dev-dashboards/panel-graph/graph-ng-nulls.json +++ b/devenv/dev-dashboards/panel-graph/graph-ng-nulls.json @@ -17,11 +17,14 @@ "graphTooltip": 0, "links": [ { - "icon": "external link", - "tags": ["gdev", "graph-ng"], - "type": "dashboards", "asDropdown": true, - "title": "Graph Tests" + "icon": "external link", + "tags": [ + "gdev", + "graph-ng" + ], + "title": "Graph Tests", + "type": "dashboards" } ], "panels": [ @@ -35,13 +38,15 @@ "custom": { "axisLabel": "", "axisPlacement": "auto", + "barAlignment": 0, "drawStyle": "line", - "fillGradient": "none", "fillOpacity": 0, + "gradientMode": "none", "hideFrom": { "graph": false, "legend": false, - "tooltip": false + "tooltip": false, + "viz": false }, "lineInterpolation": "linear", "lineWidth": 2, @@ -50,10 +55,16 @@ "type": "linear" }, "showPoints": "auto", - "spanNulls": false + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], - "nullValueMode": "null", "thresholds": { "mode": "absolute", "steps": [ @@ -82,6 +93,7 @@ "options": { "graph": {}, "legend": { + "calcs": [], "displayMode": "list", "placement": "bottom" }, @@ -136,13 +148,15 @@ "custom": { "axisLabel": "", "axisPlacement": "auto", + "barAlignment": 0, "drawStyle": "line", - "fillGradient": "none", "fillOpacity": 0, + "gradientMode": "none", "hideFrom": { "graph": false, "legend": false, - "tooltip": false + "tooltip": false, + "viz": false }, "lineInterpolation": "linear", "lineWidth": 2, @@ -151,11 +165,17 @@ "type": "linear" }, "showPoints": "auto", - "spanNulls": false + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "max": 120, - "nullValueMode": "null", "thresholds": { "mode": "absolute", "steps": [ @@ -184,6 +204,7 @@ "options": { "graph": {}, "legend": { + "calcs": [], "displayMode": "list", "placement": "bottom" }, @@ -246,13 +267,15 @@ "custom": { "axisLabel": "", "axisPlacement": "auto", + "barAlignment": 0, "drawStyle": "line", - "fillGradient": "none", "fillOpacity": 0, + "gradientMode": "none", "hideFrom": { "graph": false, "legend": false, - "tooltip": false + "tooltip": false, + "viz": false }, "lineInterpolation": "linear", "lineWidth": 2, @@ -261,11 +284,17 @@ "type": "linear" }, "showPoints": "auto", - "spanNulls": false + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "max": 120, - "nullValueMode": "null", "thresholds": { "mode": "absolute", "steps": [ @@ -294,6 +323,7 @@ "options": { "graph": {}, "legend": { + "calcs": [], "displayMode": "list", "placement": "bottom" }, @@ -375,13 +405,15 @@ "custom": { "axisLabel": "", "axisPlacement": "auto", + "barAlignment": 0, "drawStyle": "line", - "fillGradient": "none", "fillOpacity": 0, + "gradientMode": "none", "hideFrom": { "graph": false, "legend": false, - "tooltip": false + "tooltip": false, + "viz": false }, "lineInterpolation": "linear", "lineWidth": 2, @@ -390,10 +422,16 @@ "type": "linear" }, "showPoints": "auto", - "spanNulls": true + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], - "nullValueMode": "null", "thresholds": { "mode": "absolute", "steps": [ @@ -422,6 +460,7 @@ "options": { "graph": {}, "legend": { + "calcs": [], "displayMode": "list", "placement": "bottom" }, @@ -476,13 +515,15 @@ "custom": { "axisLabel": "", "axisPlacement": "auto", + "barAlignment": 0, "drawStyle": "line", - "fillGradient": "none", "fillOpacity": 0, + "gradientMode": "none", "hideFrom": { "graph": false, "legend": false, - "tooltip": false + "tooltip": false, + "viz": false }, "lineInterpolation": "linear", "lineWidth": 2, @@ -491,11 +532,17 @@ "type": "linear" }, "showPoints": "auto", - "spanNulls": true + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "max": 120, - "nullValueMode": "null", "thresholds": { "mode": "absolute", "steps": [ @@ -524,6 +571,7 @@ "options": { "graph": {}, "legend": { + "calcs": [], "displayMode": "list", "placement": "bottom" }, @@ -586,13 +634,15 @@ "custom": { "axisLabel": "", "axisPlacement": "auto", + "barAlignment": 0, "drawStyle": "line", - "fillGradient": "none", "fillOpacity": 0, + "gradientMode": "none", "hideFrom": { "graph": false, "legend": false, - "tooltip": false + "tooltip": false, + "viz": false }, "lineInterpolation": "linear", "lineWidth": 2, @@ -601,11 +651,17 @@ "type": "linear" }, "showPoints": "auto", - "spanNulls": true + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "max": 120, - "nullValueMode": "null", "thresholds": { "mode": "absolute", "steps": [ @@ -634,6 +690,7 @@ "options": { "graph": {}, "legend": { + "calcs": [], "displayMode": "list", "placement": "bottom" }, @@ -713,48 +770,33 @@ "mode": "palette-classic" }, "custom": { - "axis": { - "grid": true, - "label": "Temperature", - "side": 3, - "width": 60 - }, "axisLabel": "", "axisPlacement": "auto", - "bars": { - "show": false - }, + "barAlignment": 0, "drawStyle": "line", - "fill": { - "alpha": 0 - }, - "fillGradient": "none", "fillOpacity": 10, + "gradientMode": "none", "hideFrom": { "graph": false, "legend": false, - "tooltip": false - }, - "line": { - "color": { - "mode": "palette-classic" - }, - "show": true, - "width": 1 + "tooltip": false, + "viz": false }, "lineInterpolation": "linear", "lineWidth": 2, - "nullValues": "null", "pointSize": 5, - "points": { - "radius": 8, - "show": true - }, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", - "spanNulls": false + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { @@ -818,6 +860,7 @@ }, "legend": { "asTable": false, + "calcs": [], "displayMode": "list", "isVisible": true, "placement": "bottom" @@ -874,48 +917,33 @@ "mode": "palette-classic" }, "custom": { - "axis": { - "grid": true, - "label": "Temperature", - "side": 3, - "width": 60 - }, "axisLabel": "", "axisPlacement": "auto", - "bars": { - "show": false - }, + "barAlignment": 0, "drawStyle": "line", - "fill": { - "alpha": 0 - }, - "fillGradient": "none", "fillOpacity": 10, + "gradientMode": "none", "hideFrom": { "graph": false, "legend": false, - "tooltip": false - }, - "line": { - "color": { - "mode": "palette-classic" - }, - "show": true, - "width": 1 + "tooltip": false, + "viz": false }, "lineInterpolation": "linear", "lineWidth": 2, - "nullValues": "null", "pointSize": 5, - "points": { - "radius": 8, - "show": true - }, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", - "spanNulls": false + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { @@ -979,6 +1007,7 @@ }, "legend": { "asTable": false, + "calcs": [], "displayMode": "list", "isVisible": true, "placement": "bottom" @@ -1044,7 +1073,8 @@ "hideFrom": { "graph": false, "legend": false, - "tooltip": false + "tooltip": false, + "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, @@ -1053,7 +1083,14 @@ "type": "linear" }, "showPoints": "never", - "spanNulls": 3600000 + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { @@ -1124,11 +1161,97 @@ "timeShift": null, "title": "Span nulls below 1hr", "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 7, + "x": 0, + "y": 22 + }, + "id": 15, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "30,null,1,20,90,null,30,null,5,0,null,30" + } + ], + "title": "Always show points between gaps", + "type": "timeseries" } ], - "schemaVersion": 27, + "schemaVersion": 30, "style": "dark", - "tags": ["gdev", "panel-tests", "graph-ng"], + "tags": [ + "gdev", + "panel-tests", + "graph-ng" + ], "templating": { "list": [] }, @@ -1140,5 +1263,5 @@ "timezone": "", "title": "Panel Tests - Graph NG - Gaps and Connected", "uid": "8mmCAF1Mz", - "version": 1 -} + "version": 12 +} \ No newline at end of file diff --git a/packages/grafana-ui/src/components/GraphNG/__snapshots__/utils.test.ts.snap b/packages/grafana-ui/src/components/GraphNG/__snapshots__/utils.test.ts.snap index 3059ee6ffc5..a41a2415cba 100644 --- a/packages/grafana-ui/src/components/GraphNG/__snapshots__/utils.test.ts.snap +++ b/packages/grafana-ui/src/components/GraphNG/__snapshots__/utils.test.ts.snap @@ -127,6 +127,7 @@ Object { "paths": [Function], "points": Object { "fill": "#808080", + "filter": [Function], "show": true, "size": undefined, "stroke": "#808080", @@ -147,6 +148,7 @@ Object { "paths": [Function], "points": Object { "fill": "#808080", + "filter": [Function], "show": true, "size": undefined, "stroke": "#808080", @@ -167,6 +169,7 @@ Object { "paths": [Function], "points": Object { "fill": "#808080", + "filter": [Function], "show": true, "size": undefined, "stroke": "#808080", @@ -187,6 +190,7 @@ Object { "paths": [Function], "points": Object { "fill": "#808080", + "filter": [Function], "show": true, "size": undefined, "stroke": "#808080", @@ -207,6 +211,7 @@ Object { "paths": [Function], "points": Object { "fill": "#808080", + "filter": [Function], "show": true, "size": undefined, "stroke": "#808080", diff --git a/packages/grafana-ui/src/components/TimeSeries/utils.ts b/packages/grafana-ui/src/components/TimeSeries/utils.ts index e7331791b5f..ff977febb8f 100644 --- a/packages/grafana-ui/src/components/TimeSeries/utils.ts +++ b/packages/grafana-ui/src/components/TimeSeries/utils.ts @@ -25,6 +25,7 @@ import { ScaleOrientation, } from '../uPlot/config'; import { collectStackingGroups } from '../uPlot/utils'; +import uPlot from 'uplot'; const defaultFormatter = (v: any) => (v == null ? '-' : v.toFixed(1)); @@ -149,6 +150,43 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor const showPoints = customConfig.drawStyle === DrawStyle.Points ? PointVisibility.Always : customConfig.showPoints; + let pointsFilter: uPlot.Series.Points.Filter = () => null; + + if (customConfig.spanNulls !== true) { + pointsFilter = (u, seriesIdx, show, gaps) => { + let filtered = []; + + let series = u.series[seriesIdx]; + + if (!show && gaps && gaps.length) { + const [firstIdx, lastIdx] = series.idxs!; + const xData = u.data[0]; + const firstPos = Math.round(u.valToPos(xData[firstIdx], 'x', true)); + const lastPos = Math.round(u.valToPos(xData[lastIdx], 'x', true)); + + if (gaps[0][0] === firstPos) { + filtered.push(firstIdx); + } + + // show single points between consecutive gaps that share end/start + for (let i = 0; i < gaps.length; i++) { + let thisGap = gaps[i]; + let nextGap = gaps[i + 1]; + + if (nextGap && thisGap[1] === nextGap[0]) { + filtered.push(u.posToIdx(thisGap[1], true)); + } + } + + if (gaps[gaps.length - 1][1] === lastPos) { + filtered.push(lastIdx); + } + } + + return filtered.length ? filtered : null; + }; + } + let { fillOpacity } = customConfig; if (customConfig.fillBelowTo && field.state?.origin) { @@ -175,6 +213,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor builder.addSeries({ scaleKey, showPoints, + pointsFilter, colorMode, fillOpacity, theme, 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 cd62a9366aa..40f4f7bf1ab 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts @@ -497,6 +497,7 @@ describe('UPlotConfigBuilder', () => { "paths": [Function], "points": Object { "fill": "#00ff00", + "filter": undefined, "size": 5, "stroke": "#00ff00", }, @@ -606,6 +607,7 @@ describe('UPlotConfigBuilder', () => { "paths": [Function], "points": Object { "fill": undefined, + "filter": undefined, "size": undefined, "stroke": undefined, }, @@ -621,6 +623,7 @@ describe('UPlotConfigBuilder', () => { "paths": [Function], "points": Object { "fill": undefined, + "filter": undefined, "size": 5, "stroke": undefined, }, @@ -636,6 +639,7 @@ describe('UPlotConfigBuilder', () => { "paths": [Function], "points": Object { "fill": undefined, + "filter": undefined, "size": 5, "stroke": undefined, }, diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts index bc6b98b0665..65b235a9962 100755 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts @@ -25,6 +25,7 @@ export interface SeriesProps extends LineConfig, BarConfig, FillConfig, PointsCo colorMode?: FieldColorMode; drawStyle?: DrawStyle; pathBuilder?: Series.PathBuilder; + pointsFilter?: Series.Points.Filter; pointsBuilder?: Series.Points.Show; show?: boolean; dataFrameFieldIndex?: DataFrameFieldIndex; @@ -37,6 +38,7 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder { drawStyle, pathBuilder, pointsBuilder, + pointsFilter, lineInterpolation, lineWidth, lineStyle, @@ -86,6 +88,7 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder { stroke: pointColor, fill: pointColor, size: pointSize, + filter: pointsFilter, }, }; diff --git a/public/app/plugins/panel/barchart/__snapshots__/utils.test.ts.snap b/public/app/plugins/panel/barchart/__snapshots__/utils.test.ts.snap index bb9a83cfa37..9cf07c2de24 100644 --- a/public/app/plugins/panel/barchart/__snapshots__/utils.test.ts.snap +++ b/public/app/plugins/panel/barchart/__snapshots__/utils.test.ts.snap @@ -107,6 +107,7 @@ Object { "paths": [Function], "points": Object { "fill": undefined, + "filter": undefined, "size": undefined, "stroke": undefined, }, @@ -229,6 +230,7 @@ Object { "paths": [Function], "points": Object { "fill": undefined, + "filter": undefined, "size": undefined, "stroke": undefined, }, @@ -351,6 +353,7 @@ Object { "paths": [Function], "points": Object { "fill": undefined, + "filter": undefined, "size": undefined, "stroke": undefined, }, @@ -473,6 +476,7 @@ Object { "paths": [Function], "points": Object { "fill": undefined, + "filter": undefined, "size": undefined, "stroke": undefined, }, @@ -595,6 +599,7 @@ Object { "paths": [Function], "points": Object { "fill": undefined, + "filter": undefined, "size": undefined, "stroke": undefined, }, @@ -717,6 +722,7 @@ Object { "paths": [Function], "points": Object { "fill": undefined, + "filter": undefined, "size": undefined, "stroke": undefined, }, @@ -839,6 +845,7 @@ Object { "paths": [Function], "points": Object { "fill": undefined, + "filter": undefined, "size": undefined, "stroke": undefined, }, @@ -961,6 +968,7 @@ Object { "paths": [Function], "points": Object { "fill": undefined, + "filter": undefined, "size": undefined, "stroke": undefined, },