From ab1da127d080a471c79fc46384e545b33b768d8e Mon Sep 17 00:00:00 2001 From: Andy McHugh Date: Fri, 12 Apr 2024 18:37:53 +0100 Subject: [PATCH 1/2] add background color drive New config terms background.darkThemeColor & background.lightThemeColor allow the background color to be driven based off of active theme. If not defined the color is not driven. --- CHANGELOG.md | 6 + .../dashboardData/backgroundColor.svg | 4 + provisioning/dashboards/backgroundColor.json | 182 ++++++++++++++++++ src/components/Config.tsx | 7 + src/components/FlowPanel.tsx | 5 +- src/components/SvgUpdater.tsx | 10 +- src/components/Utils.tsx | 11 +- yaml_defs/panelConfig.yaml | 6 + 8 files changed, 225 insertions(+), 6 deletions(-) create mode 100644 provisioning/dashboardData/backgroundColor.svg create mode 100644 provisioning/dashboards/backgroundColor.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a380b..b560f81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ Fixes x-scaling ratio when the SVG x-dimension is smaller than the available window. Now it scales with the window whereas before it scaled at twice the rate resulting in it being much smaller than necessary. +Adds the ability to drive the SVG background. New panelConfig terms: +- background.darkThemeColor +- background.lightThemeColor +Can be used with the normal color-names, rbg, hex values. When the relevant term is +undefined the background color is not driven. + ## 1.5.0 Fixes the grafana variable threshold matching to break out on first rule match for a given variable/cell tuple. Before it was continuing through diff --git a/provisioning/dashboardData/backgroundColor.svg b/provisioning/dashboardData/backgroundColor.svg new file mode 100644 index 0000000..a88a39b --- /dev/null +++ b/provisioning/dashboardData/backgroundColor.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/provisioning/dashboards/backgroundColor.json b/provisioning/dashboards/backgroundColor.json new file mode 100644 index 0000000..842ce3e --- /dev/null +++ b/provisioning/dashboards/backgroundColor.json @@ -0,0 +1,182 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 6, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Background\n\nBackground is normally left untouched but the panel yaml can define a darkTheme and a lightTheme color.\nIf the yaml field is present the background color is driven. Go to user -> profile to change the theme from dark to light.", + "mode": "markdown" + }, + "pluginVersion": "10.0.0", + "title": "Panel Title", + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 4 + }, + "id": 5, + "options": { + "debuggingCtr": { + "colorsCtr": 0, + "dataCtr": 0, + "displaySvgCtr": 0, + "mappingsCtr": 0, + "timingsCtr": 0 + }, + "siteConfig": "", + "svg": "\n\n\n", + "testDataEnabled": true, + "timeSliderEnabled": true + }, + "title": "Background Not driven", + "type": "andrewbmchugh-flow-panel" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 4 + }, + "id": 2, + "options": { + "debuggingCtr": { + "colorsCtr": 0, + "dataCtr": 0, + "displaySvgCtr": 0, + "mappingsCtr": 0, + "timingsCtr": 0 + }, + "panelConfig": "---\n\nbackground:\n darkThemeColor: \"yellow\"\n lightThemeColor: \"green\"", + "siteConfig": "", + "svg": "\n\n\n", + "testDataEnabled": true, + "timeSliderEnabled": true + }, + "title": "Background - darkTheme=yellow, lightTheme=green", + "type": "andrewbmchugh-flow-panel" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 13 + }, + "id": 7, + "options": { + "debuggingCtr": { + "colorsCtr": 0, + "dataCtr": 0, + "displaySvgCtr": 0, + "mappingsCtr": 0, + "timingsCtr": 0 + }, + "panelConfig": "---\n\nbackground:\n darkThemeColor: \"#0000ff\"\n lightThemeColor: \"#ff0000\"", + "siteConfig": "", + "svg": "\n\n\n", + "testDataEnabled": true, + "timeSliderEnabled": true + }, + "title": "Background - darkTheme=#0000ff, lightTheme=#ff0000", + "type": "andrewbmchugh-flow-panel" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 13 + }, + "id": 6, + "options": { + "debuggingCtr": { + "colorsCtr": 0, + "dataCtr": 0, + "displaySvgCtr": 0, + "mappingsCtr": 0, + "timingsCtr": 0 + }, + "panelConfig": "---\n\nbackground:\n darkThemeColor: \"rgb(0,0,255)\"\n lightThemeColor: \"rgb(255,0,0)\"", + "siteConfig": "", + "svg": "\n\n\n", + "testDataEnabled": true, + "timeSliderEnabled": true + }, + "title": "Background - darkTheme=rgb(0,0,255), lightTheme=rgb(255,0,0)", + "type": "andrewbmchugh-flow-panel" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Background Color", + "version": 12, + "weekStart": "" + } \ No newline at end of file diff --git a/src/components/Config.tsx b/src/components/Config.tsx index 20fc3d0..101394f 100644 --- a/src/components/Config.tsx +++ b/src/components/Config.tsx @@ -18,6 +18,11 @@ export type Link = { params: LinkUrlParams; }; +export type Background = { + darkThemeColor: string | undefined; + lightThemeColor: string | undefined; +} + export type PanelConfigCellLabel = { dataRef: string | undefined; separator: LabelSeparator; @@ -49,6 +54,7 @@ export type SiteConfig = { }; export type PanelConfig = { + background: Background; variableThresholdScalars: Map; gradientMode: ColorGradientMode; cellIdPreamble: string; @@ -60,6 +66,7 @@ export type PanelConfig = { export function panelConfigFactory(config: any) { config = config || {}; return { + background: config.background || {}, variableThresholdScalars: new Map(Object.entries(config.variableThresholdScalars || {})), gradientMode: config.gradientMode || 'none', cellIdPreamble: config.cellIdPreamble || '', diff --git a/src/components/FlowPanel.tsx b/src/components/FlowPanel.tsx index ed2a58e..6cac72c 100644 --- a/src/components/FlowPanel.tsx +++ b/src/components/FlowPanel.tsx @@ -132,9 +132,10 @@ export const FlowPanel: React.FC = ({ options, data, width, height, timeZ if (svgStr && panelConfig && siteConfig) { configInit(siteConfig, panelConfig); + const gt = grafanaTheme.current; const svgDoc = new DOMParser().parseFromString(sanitizeSvgStr(svgStr), "text/xml"); - const svgAttribs = svgInit(svgDoc, panelConfig, siteConfig); - primeColorCache(grafanaTheme.current, svgAttribs); + const svgAttribs = svgInit(svgDoc, gt.isDark, panelConfig, siteConfig); + primeColorCache(gt, svgAttribs, panelConfig.background); svgHolderRef.current = { doc: svgDoc, attribs: svgAttribs, diff --git a/src/components/SvgUpdater.tsx b/src/components/SvgUpdater.tsx index 24ef6da..3102516 100644 --- a/src/components/SvgUpdater.tsx +++ b/src/components/SvgUpdater.tsx @@ -5,7 +5,7 @@ import { SiteConfig, VariableThresholdScalars } from 'components/Config'; import { TimeSeriesData } from 'components/TimeSeries'; import { - cellIdFactory, CellIdMaker, getColor, + cellIdFactory, CellIdMaker, colorLookup, getColor, variableThresholdScalarsInit, variableThresholdScaleValue } from 'components/Utils'; // Defines the metadata stored against each drivable svg cell @@ -109,7 +109,7 @@ function recurseElements(el: HTMLElement, cellData: SvgCell, cellIdMaker: CellId return false; } -export function svgInit(doc: Document, panelConfig: PanelConfig, siteConfig: SiteConfig): SvgAttribs { +export function svgInit(doc: Document, isDark: boolean, panelConfig: PanelConfig, siteConfig: SiteConfig): SvgAttribs { let cells = new Map(); const cellIdPreamble = panelConfig.cellIdPreamble; panelConfig.cells.forEach((cellProps, cellIdShort) => { @@ -155,6 +155,12 @@ export function svgInit(doc: Document, panelConfig: PanelConfig, siteConfig: Sit // image won't scale and center corrently let dimensions = dimensionCoherence(doc); + // Set background according to theme if defined in config + const bgColor = isDark ? panelConfig.background.darkThemeColor : panelConfig.background.lightThemeColor; + if (bgColor) { + doc.documentElement.style.backgroundColor = colorLookup(bgColor); + } + return { width: dimensions.width, height: dimensions.height, diff --git a/src/components/Utils.tsx b/src/components/Utils.tsx index 76e749d..f6025e5 100644 --- a/src/components/Utils.tsx +++ b/src/components/Utils.tsx @@ -1,6 +1,6 @@ import { GrafanaTheme2, colorManipulator } from '@grafana/data'; import { SvgAttribs, SvgCell } from 'components/SvgUpdater' -import { PanelConfigCellColor, Threshold ,VariableThresholdScalars } from 'components/Config'; +import { Background, PanelConfigCellColor, Threshold ,VariableThresholdScalars } from 'components/Config'; @@ -66,7 +66,7 @@ function rgbToString(rgb: number[]) { return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`; } -export function primeColorCache(theme: GrafanaTheme2, svgAttribs: SvgAttribs) { +export function primeColorCache(theme: GrafanaTheme2, svgAttribs: SvgAttribs, background: Background) { function initCache(thresholds: Threshold[] | undefined) { if (thresholds) { thresholds.forEach(function(threshold) { @@ -79,6 +79,13 @@ export function primeColorCache(theme: GrafanaTheme2, svgAttribs: SvgAttribs) { initCache(cellData.cellProps.fillColor && cellData.cellProps.fillColor.thresholds); initCache(cellData.cellProps.labelColor && cellData.cellProps.labelColor.thresholds); }); + + if (background.darkThemeColor) { + colorStringToRgb(theme, background.darkThemeColor); + } + if (background.lightThemeColor) { + colorStringToRgb(theme, background.lightThemeColor); + } } export function colorStringToRgb(theme: GrafanaTheme2, colorStr: string) { diff --git a/yaml_defs/panelConfig.yaml b/yaml_defs/panelConfig.yaml index 9d3dece..a3c84cb 100644 --- a/yaml_defs/panelConfig.yaml +++ b/yaml_defs/panelConfig.yaml @@ -30,6 +30,12 @@ variableThresholdScalars: #------------------------------------------------------------------------------ # Panel Config +# 1.6.0 onwards: When undefined the backgrond color is not driven. When defined the color for +# the current theme is used. Colors can be entered in all the normal ways of name, hex, hsl, etc. +background: + darkThemeColor: "yellow" + lightThemeColor: "green" + # This defines the default color gradientMode. It defines the default value on cell labelColor.gradientMode # and fillColor.gradientMode for when the field is undefined. Values are "hue" and "none" with # "none" being the default when undefined. From 8311489f81297c332bcccc8e236e94a8c48742bd Mon Sep 17 00:00:00 2001 From: Andy McHugh Date: Fri, 12 Apr 2024 18:53:30 +0100 Subject: [PATCH 2/2] correct color application ordering --- src/components/FlowPanel.tsx | 6 ++---- src/components/SvgUpdater.tsx | 24 ++++++++++++++++-------- yaml_defs/panelConfig.yaml | 2 +- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/components/FlowPanel.tsx b/src/components/FlowPanel.tsx index 6cac72c..21b6ee1 100644 --- a/src/components/FlowPanel.tsx +++ b/src/components/FlowPanel.tsx @@ -10,7 +10,7 @@ import { svgInit, svgUpdate, SvgHolder } from 'components/SvgUpdater'; import { seriesExtend, seriesInterpolate , seriesTransform } from 'components/TimeSeries'; import { TimeSliderFactory } from 'components/TimeSlider'; import { displayColorsInner, displayDataInner, displayMappingsInner, displaySvgInner } from 'components/DebuggingEditor'; -import { primeColorCache, appendUrlParams, getInstrumenter } from 'components/Utils'; +import { appendUrlParams, getInstrumenter } from 'components/Utils'; import { addHook, sanitize } from 'dompurify'; interface Props extends PanelProps {} @@ -132,10 +132,8 @@ export const FlowPanel: React.FC = ({ options, data, width, height, timeZ if (svgStr && panelConfig && siteConfig) { configInit(siteConfig, panelConfig); - const gt = grafanaTheme.current; const svgDoc = new DOMParser().parseFromString(sanitizeSvgStr(svgStr), "text/xml"); - const svgAttribs = svgInit(svgDoc, gt.isDark, panelConfig, siteConfig); - primeColorCache(gt, svgAttribs, panelConfig.background); + const svgAttribs = svgInit(svgDoc, grafanaTheme.current, panelConfig, siteConfig); svgHolderRef.current = { doc: svgDoc, attribs: svgAttribs, diff --git a/src/components/SvgUpdater.tsx b/src/components/SvgUpdater.tsx index 3102516..30d8ea8 100644 --- a/src/components/SvgUpdater.tsx +++ b/src/components/SvgUpdater.tsx @@ -1,4 +1,4 @@ -import { getValueFormatterIndex, formattedValueToString } from '@grafana/data'; +import { getValueFormatterIndex, formattedValueToString, GrafanaTheme2 } from '@grafana/data'; import { LabelSeparator, Link, PanelConfig, PanelConfigCell, PanelConfigCellColor, PanelConfigCellLabel, @@ -6,6 +6,7 @@ import { import { TimeSeriesData } from 'components/TimeSeries'; import { cellIdFactory, CellIdMaker, colorLookup, getColor, + primeColorCache, variableThresholdScalarsInit, variableThresholdScaleValue } from 'components/Utils'; // Defines the metadata stored against each drivable svg cell @@ -109,7 +110,7 @@ function recurseElements(el: HTMLElement, cellData: SvgCell, cellIdMaker: CellId return false; } -export function svgInit(doc: Document, isDark: boolean, panelConfig: PanelConfig, siteConfig: SiteConfig): SvgAttribs { +export function svgInit(doc: Document, grafanaTheme: GrafanaTheme2, panelConfig: PanelConfig, siteConfig: SiteConfig): SvgAttribs { let cells = new Map(); const cellIdPreamble = panelConfig.cellIdPreamble; panelConfig.cells.forEach((cellProps, cellIdShort) => { @@ -155,13 +156,9 @@ export function svgInit(doc: Document, isDark: boolean, panelConfig: PanelConfig // image won't scale and center corrently let dimensions = dimensionCoherence(doc); - // Set background according to theme if defined in config - const bgColor = isDark ? panelConfig.background.darkThemeColor : panelConfig.background.lightThemeColor; - if (bgColor) { - doc.documentElement.style.backgroundColor = colorLookup(bgColor); - } + - return { + const svgAttribs = { width: dimensions.width, height: dimensions.height, scaleDrive: dimensions.scaleDrive, @@ -169,6 +166,17 @@ export function svgInit(doc: Document, isDark: boolean, panelConfig: PanelConfig elementLinks: elementLinks, variableValues: variableValues, }; + + // Initialie the color cache and setup the background + primeColorCache(grafanaTheme, svgAttribs, panelConfig.background); + + // Set background according to theme if defined in config + const bgColor = grafanaTheme.isDark ? panelConfig.background.darkThemeColor : panelConfig.background.lightThemeColor; + if (bgColor) { + doc.documentElement.style.backgroundColor = colorLookup(bgColor); + } + + return svgAttribs; } function getCellValue(tsName: string, tsData: TimeSeriesData) { diff --git a/yaml_defs/panelConfig.yaml b/yaml_defs/panelConfig.yaml index a3c84cb..2e6aaf0 100644 --- a/yaml_defs/panelConfig.yaml +++ b/yaml_defs/panelConfig.yaml @@ -30,7 +30,7 @@ variableThresholdScalars: #------------------------------------------------------------------------------ # Panel Config -# 1.6.0 onwards: When undefined the backgrond color is not driven. When defined the color for +# 1.6.0 onwards: When undefined the background color is not driven. When defined the color for # the current theme is used. Colors can be entered in all the normal ways of name, hex, hsl, etc. background: darkThemeColor: "yellow"