Skip to content

Commit 27408ca

Browse files
EwanMeguptarohit
andauthored
Add support for colored legend (#47)
* Add support of legends for colored graphs --------- Co-authored-by: Rohit Gupta <[email protected]>
1 parent e398e0f commit 27408ca

File tree

6 files changed

+187
-13
lines changed

6 files changed

+187
-13
lines changed

README.md

+50-13
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ Go package to make lightweight ASCII line graphs ╭┈╯.
77
![image][]
88

99
## Installation
10-
``` bash
10+
```bash
1111
go get -u github.com/guptarohit/asciigraph@latest
1212
```
1313

1414
## Usage
1515

1616
### Basic graph
1717

18-
``` go
18+
```go
1919
package main
2020

2121
import (
@@ -32,7 +32,7 @@ func main() {
3232
```
3333

3434
Running this example would render the following graph:
35-
``` bash
35+
```bash
3636
10.00 ┤ ╭╮
3737
9.00 ┤ ╭╮ ││
3838
8.00 ┤ ││ ╭╮││
@@ -46,7 +46,7 @@ Running this example would render the following graph:
4646

4747
### Multiple Series
4848

49-
``` go
49+
```go
5050
package main
5151

5252
import (
@@ -63,7 +63,7 @@ func main() {
6363
```
6464

6565
Running this example would render the following graph:
66-
``` bash
66+
```bash
6767
6.00 ┤ ╭─
6868
5.00 ┼╮ │
6969
4.00 ┤╰╮ ╭╯
@@ -75,7 +75,7 @@ Running this example would render the following graph:
7575

7676
### Colored graphs
7777

78-
``` go
78+
```go
7979
package main
8080

8181
import (
@@ -110,18 +110,54 @@ Running this example would render the following graph:
110110

111111
![colored_graph_image][]
112112

113+
### Legends for colored graphs
114+
115+
The graph can include legends for each series, making it easier to interpret.
116+
117+
```go
118+
package main
119+
120+
import (
121+
"fmt"
122+
"github.com/guptarohit/asciigraph"
123+
"math"
124+
)
125+
126+
func main() {
127+
data := make([][]float64, 3)
128+
for i := 0; i < 3; i++ {
129+
for x := -12; x <= 12; x++ {
130+
v := math.NaN()
131+
if r := 12 - i; x >= -r && x <= r {
132+
v = math.Sqrt(math.Pow(float64(r), 2)-math.Pow(float64(x), 2)) / 2
133+
}
134+
data[i] = append(data[i], v)
135+
}
136+
}
137+
graph := asciigraph.PlotMany(data,
138+
asciigraph.Precision(0),
139+
asciigraph.SeriesColors(asciigraph.Red, asciigraph.Green, asciigraph.Blue),
140+
asciigraph.SeriesLegends("Red", "Green", "Blue"),
141+
asciigraph.Caption("Series with legends"))
142+
fmt.Println(graph)
143+
}
144+
```
145+
Running this example would render the following graph:
146+
147+
![graph_with_legends_image][]
148+
113149

114150
## CLI Installation
115151

116152
This package also brings a small utility for command line usage.
117153

118154
Assuming `$GOPATH/bin` is in your `$PATH`, install CLI with following command:
119-
``` bash
155+
```bash
120156
go install github.com/guptarohit/asciigraph/cmd/asciigraph@latest
121157
```
122158

123159
or pull Docker image:
124-
``` bash
160+
```bash
125161
docker pull ghcr.io/guptarohit/asciigraph:latest
126162
```
127163

@@ -130,7 +166,7 @@ or download binaries from the [releases][] page.
130166

131167
## CLI Usage
132168

133-
``` bash ✘ 0|125  16:19:23
169+
```bash ✘ 0|125  16:19:23
134170
> asciigraph --help
135171
Usage of asciigraph:
136172
asciigraph [options]
@@ -168,18 +204,18 @@ asciigraph expects data points from stdin. Invalid values are logged to stderr.
168204
169205
170206
Feed it data points via stdin:
171-
``` bash
207+
```bash
172208
seq 1 72 | asciigraph -h 10 -c "plot data from stdin"
173209
```
174210
175211
or use Docker image:
176-
``` bash
212+
```bash
177213
seq 1 72 | docker run -i --rm ghcr.io/guptarohit/asciigraph -h 10 -c "plot data from stdin"
178214
```
179215
180216
Output:
181217
182-
``` bash
218+
```bash
183219
72.00 ┤ ╭────
184220
64.90 ┤ ╭──────╯
185221
57.80 ┤ ╭──────╯
@@ -195,7 +231,7 @@ Output:
195231
```
196232
197233
Example of **real-time graph** for data points stream via stdin:
198-
``` bash
234+
```bash
199235
ping -i.2 google.com | grep -oP '(?<=time=).*(?=ms)' --line-buffered | asciigraph -r -h 10 -w 40 -c "realtime plot data (google ping in ms) from stdin"
200236
```
201237
[![asciinema][]][7]
@@ -229,3 +265,4 @@ Feel free to make a pull request! :octocat:
229265
[asciichart]: https://github.com/kroitor/asciichart
230266
[asciinema]: https://asciinema.org/a/382383.svg
231267
[7]: https://asciinema.org/a/382383
268+
[graph_with_legends_image]: https://github.com/guptarohit/asciigraph/assets/7895001/4066ee95-55ca-42a4-8a03-e73ce20df5d3

asciigraph.go

+4
Original file line numberDiff line numberDiff line change
@@ -253,5 +253,9 @@ func PlotMany(data [][]float64, options ...Option) string {
253253
}
254254
}
255255

256+
if len(config.SeriesLegends) > 0 {
257+
addLegends(&lines, config, lenMax, config.Offset+maxWidth)
258+
}
259+
256260
return lines.String()
257261
}

asciigraph_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,19 @@ func TestPlotMany(t *testing.T) {
358358
2.00 ┤\x1b[91m╭╭\x1b[0m
359359
1.00 ┤\x1b[91m││\x1b[0m
360360
0.00 ┼\x1b[91m╯╯\x1b[0m`},
361+
{
362+
[][]float64{{0, 1, 0}, {2, 3, 4, 3, 2}},
363+
[]Option{SeriesColors(Red, Blue), SeriesLegends("Red", "Blue"),
364+
Caption("legends with caption test")},
365+
`
366+
4.00 ┤ ╭╮
367+
3.00 ┤╭╯╰╮
368+
2.00 ┼╯ ╰
369+
1.00 ┤╭╮
370+
0.00 ┼╯╰
371+
legends with caption test
372+
373+
■ Red ■ Blue`},
361374
}
362375

363376
for i := range cases {

examples/rainbow-legends/main.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"math"
6+
7+
"github.com/guptarohit/asciigraph"
8+
)
9+
10+
func main() {
11+
data := make([][]float64, 6)
12+
13+
// concentric semi-circles
14+
for i := 0; i < 6; i++ {
15+
for x := -40; x <= 40; x++ {
16+
v := math.NaN()
17+
if r := 40 - i; x >= -r && x <= r {
18+
v = math.Sqrt(math.Pow(float64(r), 2)-math.Pow(float64(x), 2)) / 2
19+
}
20+
data[i] = append(data[i], v)
21+
}
22+
}
23+
graph := asciigraph.PlotMany(data, asciigraph.Precision(0), asciigraph.SeriesColors(
24+
asciigraph.Red,
25+
asciigraph.Orange,
26+
asciigraph.Yellow,
27+
asciigraph.Green,
28+
asciigraph.Blue,
29+
asciigraph.Purple,
30+
), asciigraph.SeriesLegends(
31+
"Red",
32+
"Orange",
33+
"Yellow",
34+
"Green",
35+
"Blue",
36+
"Purple",
37+
),
38+
asciigraph.Caption("Rainbow with color legends"))
39+
40+
fmt.Println(graph)
41+
// Output:
42+
// 20 ┤
43+
// 20 ┤ ╭───────╭╮───────╮
44+
// 19 ┤ ╭──╭───╭───────╭╮───────╮───╮──╮
45+
// 18 ┤ ╭─╭──╭─╭───╭───────╭╮───────╮───╮─╮──╮─╮
46+
// 17 ┤ ╭─╭─╭─╭─╭──╭──────────╯╰──────────╮──╮─╮─╮─╮─╮
47+
// 16 ┤ ╭─╭─╭╭─╭─╭────╯ ╰────╮─╮─╮╮─╮─╮
48+
// 15 ┤ ╭╭─╭─╭╭─╭──╯ ╰──╮─╮╮─╮─╮╮
49+
// 14 ┤ ╭╭─╭╭─╭╭──╯ ╰──╮╮─╮╮─╮╮
50+
// 13 ┤ ╭─╭╭╭─╭╭─╯ ╰─╮╮─╮╮╮─╮
51+
// 12 ┤ ╭╭╭─╭╭╭─╯ ╰─╮╮╮─╮╮╮
52+
// 11 ┤ ╭─╭╭╭╭╭─╯ ╰─╮╮╮╮╮─╮
53+
// 10 ┤ ╭╭─╭╭╭╭╯ ╰╮╮╮╮─╮╮
54+
// 9 ┤ ╭╭╯╭╭╭╭╯ ╰╮╮╮╮╰╮╮
55+
// 8 ┤ ╭╭╯╭╭╭╭╯ ╰╮╮╮╮╰╮╮
56+
// 7 ┤ ││╭╭╭╭╯ ╰╮╮╮╮││
57+
// 6 ┤ ╭╭╭╭╭╭╯ ╰╮╮╮╮╮╮
58+
// 5 ┤ ││││││ ││││││
59+
// 4 ┤╭╭╭╭╭╭╯ ╰╮╮╮╮╮╮
60+
// 3 ┤││││││ ││││││
61+
// 2 ┤││││││ ││││││
62+
// 1 ┤││││││ ││││││
63+
// 0 ┼╶╶╶╶╶╯ ╰╴╴╴╴╴
64+
// Rainbow with color legends
65+
//
66+
// ■ Red ■ Orange ■ Yellow ■ Green ■ Blue ■ Purple
67+
}

legend.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package asciigraph
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"strings"
7+
"unicode/utf8"
8+
)
9+
10+
// Create legend item as a colored box and text
11+
func createLegendItem(text string, color AnsiColor) (string, int) {
12+
return fmt.Sprintf(
13+
"%s■%s %s",
14+
color.String(),
15+
Default.String(),
16+
text,
17+
),
18+
// Can't use len() because of AnsiColor, add 2 for box and space
19+
utf8.RuneCountInString(text) + 2
20+
}
21+
22+
// Add legend for each series added to the graph
23+
func addLegends(lines *bytes.Buffer, config *config, lenMax int, leftPad int) {
24+
lines.WriteString("\n\n")
25+
lines.WriteString(strings.Repeat(" ", leftPad))
26+
27+
var legendsText string
28+
var legendsTextLen int
29+
rightPad := 3
30+
for i, text := range config.SeriesLegends {
31+
item, itemLen := createLegendItem(text, config.SeriesColors[i])
32+
legendsText += item
33+
legendsTextLen += itemLen
34+
35+
if i < len(config.SeriesLegends)-1 {
36+
legendsText += strings.Repeat(" ", rightPad)
37+
legendsTextLen += rightPad
38+
}
39+
}
40+
41+
if legendsTextLen < lenMax {
42+
lines.WriteString(strings.Repeat(" ", (lenMax-legendsTextLen)/2))
43+
}
44+
lines.WriteString(legendsText)
45+
}

options.go

+8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type config struct {
2020
AxisColor AnsiColor
2121
LabelColor AnsiColor
2222
SeriesColors []AnsiColor
23+
SeriesLegends []string
2324
}
2425

2526
// An optionFunc applies an option.
@@ -116,3 +117,10 @@ func SeriesColors(ac ...AnsiColor) Option {
116117
c.SeriesColors = ac
117118
})
118119
}
120+
121+
// SeriesLegends sets the legend text for the corresponding series.
122+
func SeriesLegends(text ...string) Option {
123+
return optionFunc(func(c *config) {
124+
c.SeriesLegends = text
125+
})
126+
}

0 commit comments

Comments
 (0)