Commit
Author: Kevin Schoon [me@kevinschoon.com]
Hash: 8eee8228a3afd617451857dd047f6077dab41ddb
Timestamp: Fri, 23 Aug 2024 15:25:26 +0000 (3 months ago)

+125 -2 +/-3 browse
Add the ability to configure chart colors
Add the ability to configure chart colors

Retains all of the default chart themes but provides the ability to set an
array of colors used within the chart as well as the background and foreground
color.
1diff --git a/README.md b/README.md
2index c4e192d..052a0c9 100644
3--- a/README.md
4+++ b/README.md
5 @@ -38,5 +38,8 @@ set the query parameters as appropriate:
6 - **label**: template for the chart legend in Go's text/template format. All
7 values provided by prometheus as tags are available - to label each matching
8 result by the instance, for instance, use `{{.instance}}`
9+ - **colors**: comma separated list of RGB/RGBA colors
10+ - **foreground-color**: the foreground color of the generated chart
11+ - **background-color**: the background color of the generated chart
12
13 [1]: https://golang.org/pkg/time/#ParseDuration
14 diff --git a/main.go b/main.go
15index 06d6ff5..e5bd427 100644
16--- a/main.go
17+++ b/main.go
18 @@ -2,6 +2,7 @@ package main
19
20 import (
21 "bytes"
22+ "encoding/hex"
23 "encoding/json"
24 "fmt"
25 "image/color"
26 @@ -147,6 +148,83 @@ func handleLabel(p *plot.Plot, l *plotter.Line, label string, metric map[string]
27 }
28 }
29
30+ func hexToColor(hexstr string) (color.RGBA, error) {
31+ if hexstr[0] == '#' {
32+ hexstr = hexstr[1:]
33+ }
34+
35+ decoded, err := hex.DecodeString(hexstr)
36+ if err != nil {
37+ return color.RGBA{}, err
38+ }
39+
40+ if len(decoded) != 3 && len(decoded) != 4 {
41+ return color.RGBA{}, fmt.Errorf("Hex string should be 3 or 4 bytes long")
42+ }
43+
44+ if len(decoded) == 3 {
45+ return color.RGBA{R: decoded[0], G: decoded[1], B: decoded[2], A: 255}, nil
46+ } else {
47+ return color.RGBA{R: decoded[0], G: decoded[1], B: decoded[2], A: decoded[3]}, nil
48+ }
49+ }
50+
51+ func getColors(args url.Values) ([]color.Color, error) {
52+ colorsArg := args.Get("colors")
53+ if colorsArg == "" {
54+ // default to soft colors
55+ return plotutil.SoftColors, nil
56+ }
57+ colors := []color.Color{}
58+ for _, hexString := range strings.Split(colorsArg, ",") {
59+ color, err := hexToColor(hexString)
60+ if err != nil {
61+ return nil, err
62+ }
63+ colors = append(colors, color)
64+ }
65+ return colors, nil
66+ }
67+
68+ // set the various colors of the plot configured in url arguments
69+ func setColors(plot *plot.Plot, args url.Values) ([]color.Color, error) {
70+ foregroundColor := color.RGBA{0, 0, 0, 0}
71+ if textArg, ok := args["foreground-color"]; ok {
72+ text, err := hexToColor(textArg[0])
73+ if err != nil {
74+ return nil, err
75+ }
76+ foregroundColor = text
77+ }
78+
79+ backgroundColor := color.RGBA{255, 255, 255, 255}
80+ if bgArg, ok := args["background-color"]; ok {
81+ bg, err := hexToColor(bgArg[0])
82+ if err != nil {
83+ return nil, err
84+ }
85+ backgroundColor = bg
86+ }
87+
88+ plot.Title.Color = foregroundColor
89+ plot.Legend.Color = foregroundColor
90+
91+ plot.X.Color = foregroundColor
92+ plot.X.Label.Color = foregroundColor
93+ plot.X.LineStyle.Color = foregroundColor
94+ plot.X.Tick.Label.Color = foregroundColor
95+
96+ plot.Y.Color = foregroundColor
97+ plot.Y.Label.Color = foregroundColor
98+ plot.Y.LineStyle.Color = foregroundColor
99+ plot.Y.Tick.Label.Color = foregroundColor
100+
101+ plot.BackgroundColor = backgroundColor
102+
103+ return getColors(args)
104+
105+ }
106+
107 func registerExtension(router chi.Router, extension string, mime string) {
108 router.Get("/chart."+extension, func(w http.ResponseWriter, r *http.Request) {
109 args := r.URL.Query()
110 @@ -206,6 +284,15 @@ func registerExtension(router chi.Router, extension string, mime string) {
111 if err != nil {
112 panic(err)
113 }
114+
115+ colors, err := setColors(p, args)
116+
117+ if err != nil {
118+ w.WriteHeader(400)
119+ w.Write([]byte(fmt.Sprintf("%v", err)))
120+ return
121+ }
122+
123 if t, ok := args["title"]; ok {
124 p.Title.Text = t[0]
125 }
126 @@ -227,7 +314,6 @@ func registerExtension(router chi.Router, extension string, mime string) {
127
128 plotters := make([]plot.Plotter, len(data))
129 var nextColor int
130- colors := plotutil.SoftColors
131 for i, res := range data {
132 var points plotter.XYs
133 for j, d := range res.Values {
134 diff --git a/static/index.html b/static/index.html
135index e1e6bcf..f363bb6 100644
136--- a/static/index.html
137+++ b/static/index.html
138 @@ -165,6 +165,39 @@
139 <td colspan="3"></td>
140 <td colspan="6">
141 <div class="form-field">
142+ <label for="colors">Plot Colors</label>
143+ <input type="text" id="colors" />
144+ <span class="help">
145+ Comma separated RGB/RGBA values
146+ </span>
147+ </div>
148+ </td>
149+ </tr>
150+ <tr>
151+ <td colspan="3"></td>
152+ <td colspan="3">
153+ <div class="form-field">
154+ <label for="background-color">Background Color</label>
155+ <input type="text" id="background-color" />
156+ <span class="help">
157+ RGB/RGBA value
158+ </span>
159+ </div>
160+ </td>
161+ <td colspan="3">
162+ <div class="form-field">
163+ <label for="foreground-color">Foreground Color</label>
164+ <input type="text" id="foreground-color" />
165+ <span class="help">
166+ RGB/RGBA value
167+ </span>
168+ </div>
169+ </td>
170+ </tr>
171+ <tr>
172+ <td colspan="3"></td>
173+ <td colspan="6">
174+ <div class="form-field">
175 <label for="url">Chart URL</label>
176 <input type="text" id="url" disabled />
177 </div>
178 @@ -210,7 +243,8 @@
179 src += "chart." + getValue("format") + "?";
180
181 var params = ["query", "title", "stacked", "since", "until", "width",
182- "height", "step", "min", "max", "label"];
183+ "height", "step", "min", "max", "label", "colors", "background-color",
184+ "foreground-color"];
185 var first = true;
186 for (var i = 0; i < params.length; ++i) {
187 var value = getValue(params[i]);