From 6a77cd43aecb45b9ffa8db5450fb7c7c287f37a3 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Fri, 13 Aug 2021 09:38:04 -0500 Subject: [PATCH] GraphNG: refactor by-value color schemes (#37670) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * GraphNG: account for top canvas padding in gradient gen for color scheme/thresholds-by-value * Updated test dashboard * Added fix for issue when scaleMin was same as threshold * fixed firefox issue * revert docs changes * update gdev dash for easier comparisons & regression spotting * refactor * optimize gradient re-gen/re-use and color more tinycolor.setAlpha() -> alpha(). update uPlot to dev build. * fix percentage steps * implement % threshold region rendering * crisp threshold line rendering * simplify * WIP: hoverpoint dynamic color interpolation * fix hover point color interp * re-use gradient gen to draw threshold areas * re-implement by-value color scales * tweak comment * mimic tinycolor behavior in colorManipulator.alpha() for empty colors * explicitly disable hover points for BarChart and Histogram * reduce test failures and required changes to tests * fix barchart tests * uPlot 1.6.15 Co-authored-by: Torkel Ödegaard Co-authored-by: Ryan McKinley --- .../graph-ng-by-value-color-schemes.json | 1325 ++++++++++------- .../src/themes/colorManipulator.ts | 4 + packages/grafana-ui/package.json | 2 +- .../uPlot/config/UPlotConfigBuilder.test.ts | 2 +- .../uPlot/config/UPlotConfigBuilder.ts | 30 +- .../uPlot/config/UPlotSeriesBuilder.ts | 28 +- .../uPlot/config/UPlotThresholds.ts | 91 +- .../components/uPlot/config/gradientFills.ts | 228 ++- .../barchart/__snapshots__/utils.test.ts.snap | 8 + public/app/plugins/panel/barchart/bars.ts | 1 + .../app/plugins/panel/histogram/Histogram.tsx | 1 + yarn.lock | 8 +- 12 files changed, 1071 insertions(+), 657 deletions(-) diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-by-value-color-schemes.json b/devenv/dev-dashboards/panel-graph/graph-ng-by-value-color-schemes.json index 29e6d92cc0c..65f44b9ba88 100644 --- a/devenv/dev-dashboards/panel-graph/graph-ng-by-value-color-schemes.json +++ b/devenv/dev-dashboards/panel-graph/graph-ng-by-value-color-schemes.json @@ -1,583 +1,862 @@ { - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "graphTooltip": 0, - "links": [], - "panels": [ + "annotations": { + "list": [ { - "datasource": "-- Dashboard --", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 37, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 5, - "gradientMode": "scheme", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 3, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 50, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" + { + "color": "orange", + "value": 15 }, - "thresholdsStyle": { - "mode": "off" + { + "color": "red", + "value": 30 } - }, - "mappings": [], - "max": 50, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "orange", - "value": 20 - }, - { - "color": "red", - "value": 30 - } - ] - }, - "unit": "degree" + ] }, - "overrides": [] + "unit": "degree" }, - "gridPos": { - "h": 7, - "w": 11, - "x": 0, - "y": 0 + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 0 + }, + "id": 11, + "maxDataPoints": 45, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" }, - "id": 9, - "maxDataPoints": 45, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,10,20,30,40,50" + } + ], + "title": "15 orange, 30 red", + "type": "timeseries" + }, + { + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 37, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 50, + "min": 20, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 15 + }, + { + "color": "red", + "value": 30 + } + ] }, - "tooltip": { - "mode": "single" - } + "unit": "degree" }, - "targets": [ - { - "panelId": 4, - "refId": "A" - } - ], - "title": "Color line by discrete tresholds", - "type": "timeseries" + "overrides": [] }, - { - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 0 + }, + "id": 12, + "maxDataPoints": 45, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,10,20,30,40,50" + } + ], + "title": "15 orange, 30 red", + "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 37, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "bars", - "fillOpacity": 84, - "gradientMode": "scheme", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 0, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 50, + "min": 20, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" + { + "color": "orange", + "value": 15 }, - "thresholdsStyle": { - "mode": "off" + { + "color": "red", + "value": 50 } + ] + }, + "unit": "degree" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 13, + "maxDataPoints": 45, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "1,10,20,30,40,50" + } + ], + "title": "15 orange, 50 red", + "type": "timeseries" + }, + { + "datasource": "-- Dashboard --", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 5, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" }, - "mappings": [], - "max": 50, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "orange", - "value": 20 - }, - { - "color": "red", - "value": 30 - } - ] + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" }, - "unit": "degree" + "thresholdsStyle": { + "mode": "off" + } }, - "overrides": [] + "mappings": [], + "max": 50, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 20 + }, + { + "color": "red", + "value": 30 + } + ] + }, + "unit": "degree" }, - "gridPos": { - "h": 7, - "w": 13, - "x": 11, - "y": 0 + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 7 + }, + "id": 9, + "maxDataPoints": 45, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" }, - "id": 4, - "interval": "80s", - "maxDataPoints": 42, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "panelId": 4, + "refId": "A" + } + ], + "title": "Color line by discrete tresholds", + "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 84, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 0, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 50, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 20 + }, + { + "color": "red", + "value": 30 + } + ] }, - "tooltip": { - "mode": "single" - } + "unit": "degree" }, - "targets": [ - { - "max": 40, - "min": 0, - "noise": 1, - "refId": "A", - "scenarioId": "random_walk", - "spread": 20, - "startValue": 1 - } - ], - "title": "Color bars by discrete thresholds", - "type": "timeseries" + "overrides": [] }, - { - "datasource": "-- Dashboard --", - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-GrYlRd" + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 7 + }, + "id": 4, + "interval": "80s", + "maxDataPoints": 42, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "max": 40, + "min": 0, + "noise": 1, + "refId": "A", + "scenarioId": "random_walk", + "spread": 20, + "startValue": 1 + } + ], + "timeFrom": null, + "title": "Color bars by discrete thresholds", + "type": "timeseries" + }, + { + "datasource": "-- Dashboard --", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "scheme", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 50, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null }, - "lineInterpolation": "smooth", - "lineWidth": 3, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" + { + "color": "green", + "value": 0 }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" + { + "color": "orange", + "value": 20 }, - "thresholdsStyle": { - "mode": "off" + { + "color": "red", + "value": 30 } - }, - "mappings": [], - "max": 50, - "min": 1, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue" - }, - { - "color": "green", - "value": 0 - }, - { - "color": "orange", - "value": 20 - }, - { - "color": "red", - "value": 30 - } - ] - }, - "unit": "degree" + ] }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 11, - "x": 0, - "y": 7 + "unit": "degree" }, - "id": 6, - "maxDataPoints": 50, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 6, + "maxDataPoints": 50, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" }, - "targets": [ - { - "panelId": 4, - "refId": "A" - } - ], - "title": "Color line by color scale", - "type": "timeseries" + "tooltip": { + "mode": "single" + } }, - { - "datasource": "-- Dashboard --", - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-GrYlRd" + "targets": [ + { + "panelId": 4, + "refId": "A" + } + ], + "title": "Color line by color scale", + "type": "timeseries" + }, + { + "datasource": "-- Dashboard --", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 64, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "bars", - "fillOpacity": 64, - "gradientMode": "scheme", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 50, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" + { + "color": "green", + "value": 0 }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" + { + "color": "orange", + "value": 20 }, - "thresholdsStyle": { - "mode": "off" + { + "color": "red", + "value": 30 } - }, - "mappings": [], - "max": 50, - "min": 1, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue" - }, - { - "color": "green", - "value": 0 - }, - { - "color": "orange", - "value": 20 - }, - { - "color": "red", - "value": 30 - } - ] - }, - "unit": "degree" + ] }, - "overrides": [] + "unit": "degree" }, - "gridPos": { - "h": 8, - "w": 13, - "x": 11, - "y": 7 - }, - "id": 10, - "maxDataPoints": 45, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 14 + }, + "id": 10, + "maxDataPoints": 45, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" }, - "targets": [ - { - "panelId": 4, - "refId": "A" - } - ], - "title": "Color bars by color scale", - "type": "timeseries" + "tooltip": { + "mode": "single" + } }, - { - "datasource": "-- Dashboard --", - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-GrYlRd" + "targets": [ + { + "panelId": 4, + "refId": "A" + } + ], + "title": "Color bars by color scale", + "type": "timeseries" + }, + { + "datasource": "-- Dashboard --", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 64, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 64, - "gradientMode": "scheme", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 50, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" + { + "color": "green", + "value": 0 }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" + { + "color": "orange", + "value": 20 }, - "thresholdsStyle": { - "mode": "off" + { + "color": "red", + "value": 30 } - }, - "mappings": [], - "max": 50, - "min": 1, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue" - }, - { - "color": "green", - "value": 0 - }, - { - "color": "orange", - "value": 20 - }, - { - "color": "red", - "value": 30 - } - ] - }, - "unit": "degree" + ] }, - "overrides": [] + "unit": "degree" }, - "gridPos": { - "h": 6, - "w": 24, - "x": 0, - "y": 15 - }, - "id": 7, - "maxDataPoints": 50, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 21 + }, + "id": 7, + "maxDataPoints": 50, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" }, - "targets": [ - { - "panelId": 4, - "refId": "A" - } - ], - "title": "Color line by color scale", - "type": "timeseries" + "tooltip": { + "mode": "single" + } }, - { - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-GrYlRd" + "targets": [ + { + "panelId": 4, + "refId": "A" + } + ], + "title": "Color line by color scale", + "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 10, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "points", - "fillOpacity": 10, - "gradientMode": "scheme", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 50, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null }, - "lineInterpolation": "smooth", - "lineWidth": 3, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" + { + "color": "green", + "value": 0 }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" + { + "color": "orange", + "value": 20 }, - "thresholdsStyle": { - "mode": "off" + { + "color": "red", + "value": 30 } - }, - "mappings": [], - "max": 50, - "min": 1, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue" - }, - { - "color": "green", - "value": 0 - }, - { - "color": "orange", - "value": 20 - }, - { - "color": "red", - "value": 30 - } - ] - }, - "unit": "degree" + ] }, - "overrides": [] + "unit": "degree" }, - "gridPos": { - "h": 13, - "w": 24, - "x": 0, - "y": 21 + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 21 + }, + "id": 8, + "maxDataPoints": 250, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" }, - "id": 8, - "maxDataPoints": 250, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "max": 45, + "min": 20, + "noise": 0, + "refId": "A", + "scenarioId": "random_walk", + "spread": 12, + "startValue": 40 }, - "targets": [ - { - "max": 45, - "min": 20, - "noise": 0, - "refId": "A", - "scenarioId": "random_walk", - "spread": 12, - "startValue": 40 - }, - { - "hide": false, - "max": 20, - "min": 1, - "noise": 0, - "refId": "B", - "scenarioId": "random_walk", - "spread": 10 - } - ], - "title": "Color line by color scale", - "type": "timeseries" - } - ], - "refresh": false, - "schemaVersion": 30, - "style": "dark", - "tags": [ - "gdev", - "panel-tests" - ], - "templating": { - "list": [] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Panel Tests - Graph NG - By value color schemes", - "uid": "aBXrJ0R7z", - "version": 20 - } + { + "hide": false, + "max": 20, + "min": 1, + "noise": 0, + "refId": "B", + "scenarioId": "random_walk", + "spread": 10 + } + ], + "title": "Color line by color scale", + "type": "timeseries" + } + ], + "refresh": false, + "schemaVersion": 30, + "style": "dark", + "tags": [ + "gdev", + "panel-tests" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Panel Tests - Graph NG - By value color schemes", + "uid": "aBXrJ0R7z", + "version": 11 +} \ No newline at end of file diff --git a/packages/grafana-data/src/themes/colorManipulator.ts b/packages/grafana-data/src/themes/colorManipulator.ts index b76552ed0c1..af7a22d3e80 100644 --- a/packages/grafana-data/src/themes/colorManipulator.ts +++ b/packages/grafana-data/src/themes/colorManipulator.ts @@ -255,6 +255,10 @@ export function emphasize(color: string, coefficient = 0.15) { * @beta */ export function alpha(color: string, value: number) { + if (color === '') { + return '#000000'; + } + value = clamp(value); // hex 6, hex 8 (w/alpha) diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 8691a9d012b..2324276eb4d 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -74,7 +74,7 @@ "react-transition-group": "4.4.1", "slate": "0.47.8", "tinycolor2": "1.4.1", - "uplot": "1.6.14" + "uplot": "1.6.15" }, "devDependencies": { "@rollup/plugin-commonjs": "16.0.0", 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 4e37c87d3d9..91a61158b07 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts @@ -436,7 +436,7 @@ describe('UPlotConfigBuilder', () => { theme: darkTheme, }); - expect(builder.getConfig().series[1].fill).toBe('rgba(255, 170, 187, 0.5)'); + expect(builder.getConfig().series[1].fill).toBe('#FFAABB80'); }); it('when fillColor is set ignore fillOpacity', () => { diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts index 1ebc7c8f0e9..7ab3a654cac 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts @@ -25,10 +25,6 @@ const cursorDefaults: Cursor = { size: (u, seriesIdx) => u.series[seriesIdx].points.size * 2, /*@ts-ignore*/ width: (u, seriesIdx, size) => size / 4, - /*@ts-ignore*/ - stroke: (u, seriesIdx) => u.series[seriesIdx].points.stroke(u, seriesIdx) + '80', - /*@ts-ignore*/ - fill: (u, seriesIdx) => u.series[seriesIdx].points.stroke(u, seriesIdx), }, focus: { prox: 30, @@ -50,6 +46,7 @@ export class UPlotConfigBuilder { private hooks: Hooks.Arrays = {}; private tz: string | undefined = undefined; private sync = false; + private frame: DataFrame | undefined = undefined; // to prevent more than one threshold per scale private thresholds: Record = {}; /** @@ -158,7 +155,10 @@ export class UPlotConfigBuilder { } setPrepData(prepData: PrepData) { - this.prepData = prepData; + this.prepData = (frame) => { + this.frame = frame; + return prepData(frame); + }; } setSync() { @@ -187,7 +187,25 @@ export class UPlotConfigBuilder { config.select = this.select; - config.cursor = merge({}, cursorDefaults, this.cursor); + const pointColorFn = (alphaHex = '') => (u: uPlot, seriesIdx: number) => { + /*@ts-ignore*/ + let s = u.series[seriesIdx].points._stroke; + + // interpolate for gradients/thresholds + if (typeof s !== 'string') { + let field = this.frame!.fields[seriesIdx]; + s = field.display!(field.values.get(u.cursor.idxs![seriesIdx]!)).color!; + } + + return s + alphaHex; + }; + + config.cursor = merge({}, cursorDefaults, this.cursor, { + points: { + stroke: pointColorFn('80'), + fill: pointColorFn(), + }, + }); config.tzDate = this.tzDate; diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts index a0df77776f6..81e9bd74338 100755 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts @@ -1,5 +1,11 @@ -import { DataFrameFieldIndex, FALLBACK_COLOR, FieldColorMode, GrafanaTheme2, ThresholdsConfig } from '@grafana/data'; -import tinycolor from 'tinycolor2'; +import { + colorManipulator, + DataFrameFieldIndex, + FALLBACK_COLOR, + FieldColorMode, + GrafanaTheme2, + ThresholdsConfig, +} from '@grafana/data'; import uPlot, { Series } from 'uplot'; import { BarAlignment, @@ -55,16 +61,18 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder { } = this.props; let lineConfig: Partial = {}; - const lineColor = this.getLineColor(); + + let lineColor = this.getLineColor(); + + // DrawStyle.Points mode also needs this for fill/stroke sharing & re-use in series.points. see getColor() below. + lineConfig.stroke = lineColor; if (pathBuilder != null) { lineConfig.paths = pathBuilder; - lineConfig.stroke = lineColor; lineConfig.width = lineWidth; } else if (drawStyle === DrawStyle.Points) { lineConfig.paths = () => null; } else if (drawStyle != null) { - lineConfig.stroke = lineColor; lineConfig.width = lineWidth; if (lineStyle && lineStyle.fill !== 'solid') { if (lineStyle.fill === 'dot') { @@ -84,10 +92,14 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder { }; } + const useColor: uPlot.Series.Stroke = + // @ts-ignore + typeof lineColor === 'string' ? lineColor : (u, seriesIdx) => u.series[seriesIdx]._stroke; + const pointsConfig: Partial = { points: { - stroke: lineColor, - fill: lineColor, + stroke: useColor, + fill: useColor, size: pointSize, filter: pointsFilter, }, @@ -153,7 +165,7 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder { return getScaleGradientFn(opacityPercent, theme, colorMode, thresholds); default: if (opacityPercent > 0) { - return tinycolor(lineColor).setAlpha(opacityPercent).toString(); + return colorManipulator.alpha(lineColor ?? '', opacityPercent); } } diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotThresholds.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotThresholds.ts index 7c059765efb..be4a2609b54 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotThresholds.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotThresholds.ts @@ -1,6 +1,7 @@ -import { getColorForTheme, GrafanaTheme2, ThresholdsConfig } from '@grafana/data'; +import { GrafanaTheme2, ThresholdsConfig, ThresholdsMode } from '@grafana/data'; import tinycolor from 'tinycolor2'; import { GraphThresholdsStyleConfig, GraphTresholdsStyleMode } from '../config'; +import { getDataRange, GradientDirection, scaleGradient } from './gradientFills'; export interface UPlotThresholdOptions { scaleKey: string; @@ -13,7 +14,6 @@ export function getThresholdsDrawHook(options: UPlotThresholdOptions) { return (u: uPlot) => { const ctx = u.ctx; const { scaleKey, thresholds, theme, config } = options; - const { steps } = thresholds; const { min: xMin, max: xMax } = u.scales.x; const { min: yMin, max: yMax } = u.scales[scaleKey]; @@ -21,6 +21,18 @@ export function getThresholdsDrawHook(options: UPlotThresholdOptions) { return; } + let { steps, mode } = thresholds; + + if (mode === ThresholdsMode.Percentage) { + let [min, max] = getDataRange(u, scaleKey); + let range = max - min; + + steps = steps.map((step) => ({ + ...step, + value: min + range * (step.value / 100), + })); + } + function addLines() { // Thresholds below a transparent threshold is treated like "less than", and line drawn previous threshold let transparentIndex = 0; @@ -40,9 +52,9 @@ export function getThresholdsDrawHook(options: UPlotThresholdOptions) { // if we are below a transparent index treat this a less then threshold, use previous thresholds color if (transparentIndex >= idx && idx > 0) { - color = tinycolor(getColorForTheme(steps[idx - 1].color, theme.v1)); + color = tinycolor(theme.visualization.getColorByName(steps[idx - 1].color)); } else { - color = tinycolor(getColorForTheme(step.color, theme.v1)); + color = tinycolor(theme.visualization.getColorByName(step.color)); } // Unless alpha specififed set to default value @@ -50,10 +62,10 @@ export function getThresholdsDrawHook(options: UPlotThresholdOptions) { color.setAlpha(0.7); } - let x0 = u.valToPos(xMin!, 'x', true); - let y0 = u.valToPos(step.value, scaleKey, true); - let x1 = u.valToPos(xMax!, 'x', true); - let y1 = u.valToPos(step.value, scaleKey, true); + let x0 = Math.round(u.valToPos(xMin!, 'x', true)); + let y0 = Math.round(u.valToPos(step.value, scaleKey, true)); + let x1 = Math.round(u.valToPos(xMax!, 'x', true)); + let y1 = Math.round(u.valToPos(step.value, scaleKey, true)); ctx.beginPath(); ctx.lineWidth = 2; @@ -66,49 +78,26 @@ export function getThresholdsDrawHook(options: UPlotThresholdOptions) { } function addAreas() { - for (let idx = 0; idx < steps.length; idx++) { - const step = steps[idx]; - - // skip thresholds that cannot be seen - if (step.value > yMax!) { - continue; - } - - // if this is the last step make the next step the same color but +Infinity - const nextStep = - idx + 1 < steps.length - ? steps[idx + 1] - : { - ...step, - value: Infinity, - }; - - let color = tinycolor(getColorForTheme(step.color, theme.v1)); - - // Ignore fully transparent colors - const alpha = color.getAlpha(); - if (alpha === 0) { - continue; - } - - /// if no alpha set automatic alpha - if (alpha === 1) { - color = color.setAlpha(0.15); - } - - let value = step.value === -Infinity ? yMin : step.value; - let nextValue = nextStep.value === Infinity || nextStep.value > yMax! ? yMax : nextStep.value; - - let x0 = u.valToPos(xMin ?? 0, 'x', true); - let y0 = u.valToPos(value ?? 0, scaleKey, true); - let x1 = u.valToPos(xMax ?? 1, 'x', true); - let y1 = u.valToPos(nextValue ?? 1, scaleKey, true); - - ctx.save(); - ctx.fillStyle = color.toString(); - ctx.fillRect(x0, y0, x1 - x0, y1 - y0); - ctx.restore(); - } + let grd = scaleGradient( + u, + u.series[1].scale!, + GradientDirection.Up, + steps.map((step) => { + let color = tinycolor(theme.visualization.getColorByName(step.color)); + + if (color.getAlpha() === 1) { + color.setAlpha(0.15); + } + + return [step.value, color.toString()]; + }), + true + ); + + ctx.save(); + ctx.fillStyle = grd; + ctx.fillRect(u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height); + ctx.restore(); } switch (config.mode) { diff --git a/packages/grafana-ui/src/components/uPlot/config/gradientFills.ts b/packages/grafana-ui/src/components/uPlot/config/gradientFills.ts index 09e8b078dfa..de1c43e8ff7 100644 --- a/packages/grafana-ui/src/components/uPlot/config/gradientFills.ts +++ b/packages/grafana-ui/src/components/uPlot/config/gradientFills.ts @@ -1,4 +1,11 @@ -import { FieldColorMode, FieldColorModeId, GrafanaTheme2, ThresholdsConfig, ThresholdsMode } from '@grafana/data'; +import { + colorManipulator, + FieldColorMode, + FieldColorModeId, + GrafanaTheme2, + ThresholdsConfig, + ThresholdsMode, +} from '@grafana/data'; import tinycolor from 'tinycolor2'; import uPlot from 'uplot'; import { getCanvasContext } from '../../../utils/measureText'; @@ -11,8 +18,8 @@ export function getOpacityGradientFn( const ctx = getCanvasContext(); const gradient = ctx.createLinearGradient(0, plot.bbox.top, 0, plot.bbox.top + plot.bbox.height); - gradient.addColorStop(0, tinycolor(color).setAlpha(opacity).toRgbString()); - gradient.addColorStop(1, tinycolor(color).setAlpha(0).toRgbString()); + gradient.addColorStop(0, colorManipulator.alpha(color, opacity)); + gradient.addColorStop(1, colorManipulator.alpha(color, 0)); return gradient; }; @@ -39,10 +46,132 @@ export function getHueGradientFn( return gradient; }; } -/** - * Experimental & quick and dirty test - * Not being used - */ + +export enum GradientDirection { + 'Right' = 0, + 'Up' = 1, +} + +type ValueStop = [value: number, color: string]; + +type ScaleValueStops = ValueStop[]; + +export function scaleGradient( + u: uPlot, + scaleKey: string, + dir: GradientDirection, + scaleStops: ScaleValueStops, + discrete = false +) { + let scale = u.scales[scaleKey]; + + // we want the stop below or at the scaleMax + // and the stop below or at the scaleMin, else the stop above scaleMin + let minStopIdx: number | null = null; + let maxStopIdx: number | null = null; + + for (let i = 0; i < scaleStops.length; i++) { + let stopVal = scaleStops[i][0]; + + if (stopVal <= scale.min! || minStopIdx == null) { + minStopIdx = i; + } + + maxStopIdx = i; + + if (stopVal >= scale.max!) { + break; + } + } + + if (minStopIdx === maxStopIdx) { + return scaleStops[minStopIdx!][1]; + } + + let minStopVal = scaleStops[minStopIdx!][0]; + let maxStopVal = scaleStops[maxStopIdx!][0]; + + if (minStopVal === -Infinity) { + minStopVal = scale.min!; + } + + if (maxStopVal === Infinity) { + maxStopVal = scale.max!; + } + + let minStopPos = Math.round(u.valToPos(minStopVal, scaleKey, true)); + let maxStopPos = Math.round(u.valToPos(maxStopVal, scaleKey, true)); + + let range = minStopPos - maxStopPos; + + let x0, y0, x1, y1; + + if (dir === GradientDirection.Up) { + x0 = x1 = 0; + y0 = minStopPos; + y1 = maxStopPos; + } else { + y0 = y1 = 0; + x0 = minStopPos; + x1 = maxStopPos; + } + + let ctx = getCanvasContext(); + + let grd = ctx.createLinearGradient(x0, y0, x1, y1); + + let prevColor: string; + + for (let i = minStopIdx!; i <= maxStopIdx!; i++) { + let s = scaleStops[i]; + + let stopPos = + i === minStopIdx ? minStopPos : i === maxStopIdx ? maxStopPos : Math.round(u.valToPos(s[0], scaleKey, true)); + + let pct = (minStopPos - stopPos) / range; + + if (discrete && i > minStopIdx!) { + grd.addColorStop(pct, prevColor!); + } + + grd.addColorStop(pct, (prevColor = s[1])); + } + + return grd; +} + +export function getDataRange(plot: uPlot, scaleKey: string) { + let sc = plot.scales[scaleKey]; + + let min = Infinity; + let max = -Infinity; + + plot.series.forEach((ser, seriesIdx) => { + if (ser.show && ser.scale === scaleKey) { + // uPlot skips finding data min/max when a scale has a pre-defined range + if (ser.min == null) { + let data = plot.data[seriesIdx]; + for (let i = 0; i < data.length; i++) { + if (data[i] != null) { + min = Math.min(min, data[i]!); + max = Math.max(max, data[i]!); + } + } + } else { + min = Math.min(min, ser.min!); + max = Math.max(max, ser.max!); + } + } + }); + + if (max === min) { + min = sc.min!; + max = sc.max!; + } + + return [min, max]; +} + export function getScaleGradientFn( opacity: number, theme: GrafanaTheme2, @@ -58,68 +187,41 @@ export function getScaleGradientFn( } return (plot: uPlot, seriesIdx: number) => { - // A uplot bug (I think) where this is called before there is bbox - // Color used for cursor highlight, not sure what to do here as this is called before we have bbox - // and only once so same color is used for all points - if (plot.bbox.top == null) { - return theme.colors.text.primary; - } - - const ctx = getCanvasContext(); - const gradient = ctx.createLinearGradient(0, plot.bbox.top, 0, plot.bbox.top + plot.bbox.height); - const canvasHeight = plot.bbox.height; - const series = plot.series[seriesIdx]; - const scale = plot.scales[series.scale!]; - const scaleMin = scale.min ?? 0; - const scaleMax = scale.max ?? 100; - const scaleRange = scaleMax - scaleMin; - - const addColorStop = (value: number, color: string) => { - const pos = plot.valToPos(value, series.scale!, true); - // when above range we get negative values here - if (pos < 0) { - return; - } - - const percent = Math.max(pos / canvasHeight, 0); - const realColor = tinycolor(theme.visualization.getColorByName(color)).setAlpha(opacity).toString(); - const colorStopPos = Math.min(percent, 1); + let scaleKey = plot.series[seriesIdx].scale!; - gradient.addColorStop(colorStopPos, realColor); - }; + let gradient: CanvasGradient | string = ''; if (colorMode.id === FieldColorModeId.Thresholds) { - for (let idx = 0; idx < thresholds.steps.length; idx++) { - const step = thresholds.steps[idx]; - - if (thresholds.mode === ThresholdsMode.Absolute) { - const value = step.value === -Infinity ? scaleMin : step.value; - addColorStop(value, step.color); - - if (thresholds.steps.length > idx + 1) { - // to make the gradient discrete - addColorStop(thresholds.steps[idx + 1].value - 0.00000001, step.color); - } - } else { - const percent = step.value === -Infinity ? 0 : step.value; - const realValue = (percent / 100) * scaleRange; - addColorStop(realValue, step.color); - - // to make the gradient discrete - if (thresholds.steps.length > idx + 1) { - // to make the gradient discrete - const nextValue = (thresholds.steps[idx + 1].value / 100) * scaleRange - 0.0000001; - addColorStop(nextValue, step.color); - } - } + if (thresholds.mode === ThresholdsMode.Absolute) { + const valueStops = thresholds.steps.map( + (step) => + [step.value, colorManipulator.alpha(theme.visualization.getColorByName(step.color), opacity)] as ValueStop + ); + gradient = scaleGradient(plot, scaleKey, GradientDirection.Up, valueStops, true); + } else { + const [min, max] = getDataRange(plot, scaleKey); + const range = max - min; + const valueStops = thresholds.steps.map( + (step) => + [ + min + range * (step.value / 100), + colorManipulator.alpha(theme.visualization.getColorByName(step.color), opacity), + ] as ValueStop + ); + gradient = scaleGradient(plot, scaleKey, GradientDirection.Up, valueStops, true); } } else if (colorMode.getColors) { const colors = colorMode.getColors(theme); - const stepValue = (scaleMax - scaleMin) / colors.length; - - for (let idx = 0; idx < colors.length; idx++) { - addColorStop(scaleMin + stepValue * idx, colors[idx]); - } + const [min, max] = getDataRange(plot, scaleKey); + const range = max - min; + const valueStops = colors.map( + (color, i) => + [ + min + range * (i / (colors.length - 1)), + colorManipulator.alpha(theme.visualization.getColorByName(color), opacity), + ] as ValueStop + ); + gradient = scaleGradient(plot, scaleKey, GradientDirection.Up, valueStops, false); } return gradient; 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 6296a591c5f..0bf6eda5f89 100644 --- a/public/app/plugins/panel/barchart/__snapshots__/utils.test.ts.snap +++ b/public/app/plugins/panel/barchart/__snapshots__/utils.test.ts.snap @@ -63,6 +63,7 @@ Object { }, "points": Object { "fill": [Function], + "show": false, "size": [Function], "stroke": [Function], "width": [Function], @@ -191,6 +192,7 @@ Object { }, "points": Object { "fill": [Function], + "show": false, "size": [Function], "stroke": [Function], "width": [Function], @@ -319,6 +321,7 @@ Object { }, "points": Object { "fill": [Function], + "show": false, "size": [Function], "stroke": [Function], "width": [Function], @@ -447,6 +450,7 @@ Object { }, "points": Object { "fill": [Function], + "show": false, "size": [Function], "stroke": [Function], "width": [Function], @@ -575,6 +579,7 @@ Object { }, "points": Object { "fill": [Function], + "show": false, "size": [Function], "stroke": [Function], "width": [Function], @@ -703,6 +708,7 @@ Object { }, "points": Object { "fill": [Function], + "show": false, "size": [Function], "stroke": [Function], "width": [Function], @@ -831,6 +837,7 @@ Object { }, "points": Object { "fill": [Function], + "show": false, "size": [Function], "stroke": [Function], "width": [Function], @@ -959,6 +966,7 @@ Object { }, "points": Object { "fill": [Function], + "show": false, "size": [Function], "stroke": [Function], "width": [Function], diff --git a/public/app/plugins/panel/barchart/bars.ts b/public/app/plugins/panel/barchart/bars.ts index 6a7ac200c26..b1fb04fd246 100644 --- a/public/app/plugins/panel/barchart/bars.ts +++ b/public/app/plugins/panel/barchart/bars.ts @@ -324,6 +324,7 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) { cursor: { x: false, y: false, + points: { show: false }, }, // scale & axis opts xValues, diff --git a/public/app/plugins/panel/histogram/Histogram.tsx b/public/app/plugins/panel/histogram/Histogram.tsx index 8f3d24decec..47690571789 100644 --- a/public/app/plugins/panel/histogram/Histogram.tsx +++ b/public/app/plugins/panel/histogram/Histogram.tsx @@ -146,6 +146,7 @@ const prepConfig = (frame: DataFrame, theme: GrafanaTheme2) => { }); builder.setCursor({ + points: { show: false }, drag: { x: true, y: false, diff --git a/yarn.lock b/yarn.lock index c1646c61f2d..13ff4f7e087 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23909,10 +23909,10 @@ update-notifier@^2.5.0: semver-diff "^2.0.0" xdg-basedir "^3.0.0" -uplot@1.6.14: - version "1.6.14" - resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.6.14.tgz#49edfaea3090a9c71d8ae389780b90635aeda3e0" - integrity sha512-I/fO/pujHe6uurtCEVy6L0Vy6/p7AclbrUGu3Mw+oW0PTGPo0khnAWLyyDqSRyMyOwIin8y5HbBEiN3g4qOLuw== +uplot@1.6.15: + version "1.6.15" + resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.6.15.tgz#785ca9f66d9c28ec2fdfb7e623d627ea0dcb0dd5" + integrity sha512-6Fgq9tMaEM9Yu9oLkKd0w7VLJtV8LHG6dBrg1TmYi0LmSLkrj2Hqr11IrHk68cMaExnWAqay6YToQCrMZt1fcQ== upper-case@^1.1.1: version "1.1.3"