Instantly share code, notes, and snippets.

# rbitr/gnuplot_jupyter_notebook.md Secret

Created May 18, 2023 14:35
Show Gist options
• Save rbitr/7bf79860c92974182bdedce79c7de0b3 to your computer and use it in GitHub Desktop.
Using Gnuplot for ascii charts inside notebooks

## Using Gnuplot for ascii charts inside notebooks

### Why

Mostly because it's fun, but the practical reasons are that you don't need matplotlib as a dependency, and it's useful to know how to do plots in the terminal.

### Plotting in the terminal

Gnuplot has a `dumb` terminal mode for ASCII plots. Note that I use 80x25 so this won't look right on smaller displays.

```% seq 10 | awk '{print \$1,  \$1*\$1}' > square.txt # numbers 1-10 and their squares
% cat square.txt
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81
10 100
% gnuplot -e 'set terminal dumb 80 25; plot "square.txt"'

100 +---------------------------------------------------------------------+
|       +       +      +       +       +       +      +       +       |
90 |-+                                              "square.txt"    A  +-|
|                                                                     |
80 |-+                                                           A     +-|
|                                                                     |
70 |-+                                                                 +-|
|                                                                     |
60 |-+                                                   A             +-|
|                                                                     |
50 |-+                                                                 +-|
|                                              A                      |
|                                                                     |
40 |-+                                    A                            +-|
|                                                                     |
30 |-+                                                                 +-|
|                              A                                      |
20 |-+                                                                 +-|
|                      A                                              |
10 |-+             A                                                   +-|
|       A       +      +       +       +       +      +       +       |
0 +---------------------------------------------------------------------+
1       2       3      4       5       6       7      8       9       10
```

Gnuplot is meant to run scripts, which you can place inline with the `-e` option. For 2D plots it wants to have the data in a space separated file, like above. To do it in a one-liner, I prefer to use process substitution:

```% gnuplot -e "set terminal dumb 80 25; plot \"<(seq 10 | awk '{print \\$1,  \\$1*\\$1}')"\"

100 +---------------------------------------------------------------------+
|       +       +      +       +       +       +      +       +       |
90 |-+                    "<(seq 10 | awk '{print \$1,  \$1*\$1}')"    A  +-|
|                                                                     |
80 |-+                                                           A     +-|
|                                                                     |
70 |-+                                                                 +-|
...```

Though escaping quotes etc becomes tricky very fast - choosing awk for the example made it even worse.

## In a Jupyter Notebook

We can run it as a shell script as is:

`!gnuplot -e "set terminal dumb 80 25; plot \"<(seq 10 | awk '{print \\$1,  \\$1*\\$1}')"\"`
``````  100 +---------------------------------------------------------------------+
|       +       +      +       +       +       +      +       +       |
90 |-+                    "<(seq 10 | awk '{print \$1,  \$1*\$1}')"    A  +-|
|                                                                     |
80 |-+                                                           A     +-|
|                                                                     |
70 |-+                                                                 +-|
|                                                                     |
60 |-+                                                   A             +-|
|                                                                     |
50 |-+                                                                 +-|
|                                              A                      |
|                                                                     |
40 |-+                                    A                            +-|
|                                                                     |
30 |-+                                                                 +-|
|                              A                                      |
20 |-+                                                                 +-|
|                      A                                              |
10 |-+             A                                                   +-|
|       A       +      +       +       +       +      +       +       |
0 +---------------------------------------------------------------------+
1       2       3      4       5       6       7      8       9       10
``````

What would be more intersting is to plot a python variable. We could write a text file, or just turn it into a string and pass to gnuplot (note the `f` string):

```data = """
1 1
2 4
3 9
4 16
5 25
6 26 # on purpose
7 49
8 64
9 81
10 100
"""

q = f""" "set terminal dumb 80 25; plot '<(echo \\"{data}\\")'" """

!gnuplot -e {q}```
``````  100 +---------------------------------------------------------------------+
|       +       +      +       +       +       +      +       +       |
90 |-+                                                              A  +-|
|                                                                     |
80 |-+                                                           A     +-|
|                                                                     |
70 |-+                                                                 +-|
|                                                                     |
60 |-+                                                   A             +-|
|                                                                     |
50 |-+                                                                 +-|
|                                              A                      |
|                                                                     |
40 |-+                                                                 +-|
|                                                                     |
30 |-+                                                                 +-|
|                              A       A                              |
20 |-+                                                                 +-|
|                      A                                              |
10 |-+             A                                                   +-|
|       A       +      +       +       +       +      +       +       |
0 +---------------------------------------------------------------------+
1       2       3      4       5       6       7      8       9       10
``````

There ends up being lots of escaping because of how escape characters get "eaten"... for example the inner quotation marks get double espaped with backslashes.

From here, we can easily dress it up a bit to make a generic 2D plot. The difference here is that we've extended it to multiple series, allowed custom marker specification, and the option to pass extra commands to gnuplot.

```import itertools
def gnuplot(series,markers=itertools.repeat("x"),extra=""):
pts = ["\n".join([f"{x} {y}" for (x,y) in s]) for s in series]
plots = ",".join([f" '<(echo \\\"{p}\\\")' pt \\\"{m}\\\" notitle " for p,m in zip(pts,markers)])
q = f""" "set terminal dumb 80 25; {extra}
plot {plots}"
"""
!gnuplot -e {q}```
`gnuplot([[(x,x*x) for x in range(1,11)]]) # has to be a list of lists`
``````  100 +---------------------------------------------------------------------+
|       +       +      +       +       +       +      +       +       |
90 |-+                                                                 +-|
|                                                                     |
80 |-+                                                           x     +-|
|                                                                     |
70 |-+                                                                 +-|
|                                                                     |
60 |-+                                                   x             +-|
|                                                                     |
50 |-+                                                                 +-|
|                                              x                      |
|                                                                     |
40 |-+                                    x                            +-|
|                                                                     |
30 |-+                                                                 +-|
|                              x                                      |
20 |-+                                                                 +-|
|                      x                                              |
10 |-+             x                                                   +-|
|       x       +      +       +       +       +      +       +       |
0 +---------------------------------------------------------------------+
1       2       3      4       5       6       7      8       9       10
``````
```import numpy as np
outer = 10*np.random.randn(200,2)
inner = np.random.randn(50,2)

extra = "set xtics out; set ytics out; set yrange [-25:25]; set xrange [-25:25];"

gnuplot([outer,inner],markers=["o","x"], extra=extra)```
``````              +             +             +            +             +
+--------------------------------------------------------------------+
|                        oo     oo                o                  |
20 +-|                                          o                    o    |-+
|                       o     o            o                         |
|                        o o  oo o                                   |
|              o    o    o o   ooo  oo    oo  o                      |
10 +-|o                     o  o o  o     o   oo    o                     |-+
|      o       o         o  o    oo    o oo      o                   |
|               o    ooo     o oo  oo  o  ooo  ooo  o oo    o        |
|    o          o ooooo      ooo xxxx  o    o  o o                   |
0 +-|   o                  oo   o o xxxxxx o ooooooo  o  o   o           |-+
|       o  o         o      o    o xx      o o      oo       o o     |
|               o  o oo  o     oo   o oo oo   o  o o                 |
|             o   o          o     o ooo o  o        o    o    o o o |
-10 +-|                          oo o    o o  oo     o   o          o      |-+
|             o        o      o  o    o  o      o                    |
|                        o                                  o        |
|         o                                  o              o        |
-20 +-|                               o                       o        o   |-+
|                                                                    |
+--------------------------------------------------------------------+
+             +             +            +             +
-20           -10            0            10            20
``````

## Conclusion

With no dependencies (other than `gnuplot` and loosely `itertools`) we can make cool ascii charts suitable for use in text files and display on a terminal, with the convenience of working in a notebook. The escaping gets messy, but haivng been sorted, I've found this good for basic 2D plots. Gnuplot has, .e.g bar plots as well that it can do in ASCII, which would be easy to add, though often don't look great.