Plotting in the Terminal — An Unconventional Approach to Data Visualization

Who knew Unicode plots could be so much fun?

Vikas Negi
Geek Culture
6 min readMay 18, 2022

--

Photo by Emily Morter on Unsplash

If you are dealing with a lot of data on a daily basis, chances are that your workflow involves using a terminal, be it on Mac, Linux or even Windows. There’s a kind of unexplanable joy in being able to perform complex tasks with ease from the command line. However, when it comes to data visualization, we often rely on fancy plotting libraries. The plot itself usually appears in a notebook, webpage or a separate window. Plotting within the terminal is mostly unheard of. Well, not anymore! Allow me to introduce you to UnicodePlots.jl, which is a Julia package supporting Unicode plots in the REPL.

I recently got some time to test this package, and to be honest, I was quite skeptical in the beginning. As I started to generate and tweak the plots, I realized how effortless and quick the process was. In the end, the results turned out to be very satisfying. I liked it so much, that I even added some plotting functions to BitcoinRPC.jl, a package which I recently developed. In case you want to know more about it, check out the link below:

Adding the package

You can add it to your current environment as shown below:

  • Press ‘]’ to enter Pkg prompt
  • add UnicodePlots

Basics

The package is designed to display plots within the REPL. Various types are supported, for example:

julia> using UnicodePlotsjulia> X = 1:10
1:10
julia> Y = rand(50:500, 10)
10-element Vector{Int64}:
270
133
365
261
480
158
470
125
391
342
julia> plt = lineplot(X, Y, title="My sample plot", xlabel="x", ylabel="y", border=:ascii)
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀My sample plot⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+-------------------------------------------------+
500 |⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀|
|⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡞⡄⠀⠀⠀⠀⠀⠀⠀⣼⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀|
|⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⢇⠀⠀⠀⠀⠀⠀⠀⡇⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀|
|⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀|
|⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡸⠀⠀⠈⡆⠀⠀⠀⠀⠀⡎⠀⢸⠀⠀⠀⠀⠀⠀⠀⡎⠢⢄⠀⠀|
|⠀⠀⠀⠀⠀⠀⠀⠀⢸⢆⠀⠀⠀⠀⢀⠇⠀⠀⠀⢣⠀⠀⠀⠀⢠⠃⠀⠀⡇⠀⠀⠀⠀⠀⢠⠃⠀⠀⠑⠤|
|⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⢣⠀⠀⠀⡸⠀⠀⠀⠀⢸⠀⠀⠀⠀⢸⠀⠀⠀⢣⠀⠀⠀⠀⠀⡜⠀⠀⠀⠀⠀|
y |⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠣⡀⢀⠇⠀⠀⠀⠀⠀⡇⠀⠀⠀⡇⠀⠀⠀⢸⠀⠀⠀⠀⢀⠇⠀⠀⠀⠀⠀|
|⡄⠀⠀⠀⠀⠀⢀⠇⠀⠀⠀⠀⠱⡜⠀⠀⠀⠀⠀⠀⢱⠀⠀⢰⠁⠀⠀⠀⠀⡇⠀⠀⠀⡸⠀⠀⠀⠀⠀⠀|
|⠱⡀⠀⠀⠀⠀⡸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡀⠀⡜⠀⠀⠀⠀⠀⢱⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀|
|⠀⢱⠀⠀⠀⢀⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⢀⠇⠀⠀⠀⠀⠀⠸⡀⠀⢸⠀⠀⠀⠀⠀⠀⠀|
|⠀⠀⢣⠀⠀⡸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⢸⠀⠀⠀⠀⠀⠀⠀⡇⠀⡇⠀⠀⠀⠀⠀⠀⠀|
|⠀⠀⠀⢇⢀⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡎⠀⠀⠀⠀⠀⠀⠀⢱⢰⠁⠀⠀⠀⠀⠀⠀⠀|
|⠀⠀⠀⠈⡾⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡎⠀⠀⠀⠀⠀⠀⠀⠀|
100 |⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀|
+-------------------------------------------------+
⠀1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀10⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀x⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

See how easy that was? One of the advantages of plotting within the REPL is speed. While using a graphical library, the first plot (especially in Julia) can take some time to appear since the requested backend needs to be compiled. That’s not the case here. Unicode plots are fast, since they are being generated directly in the terminal. How about a scatter plot?

julia> plt = lineplot(X, Y, title="My sample plot", xlabel="x", ylabel="y", border=:ascii, name = "line1")
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀My sample plot⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+-------------------------------------------------+
500 |⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀| line1
|⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡞⡄⠀⠀⠀⠀⠀⠀⠀⣼⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀|
|⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⢇⠀⠀⠀⠀⠀⠀⠀⡇⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀|
|⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀|
|⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡸⠀⠀⠈⡆⠀⠀⠀⠀⠀⡎⠀⢸⠀⠀⠀⠀⠀⠀⠀⡎⠢⢄⠀⠀|
|⠀⠀⠀⠀⠀⠀⠀⠀⢸⢆⠀⠀⠀⠀⢀⠇⠀⠀⠀⢣⠀⠀⠀⠀⢠⠃⠀⠀⡇⠀⠀⠀⠀⠀⢠⠃⠀⠀⠑⠤|
|⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⢣⠀⠀⠀⡸⠀⠀⠀⠀⢸⠀⠀⠀⠀⢸⠀⠀⠀⢣⠀⠀⠀⠀⠀⡜⠀⠀⠀⠀⠀|
y |⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠣⡀⢀⠇⠀⠀⠀⠀⠀⡇⠀⠀⠀⡇⠀⠀⠀⢸⠀⠀⠀⠀⢀⠇⠀⠀⠀⠀⠀|
|⡄⠀⠀⠀⠀⠀⢀⠇⠀⠀⠀⠀⠱⡜⠀⠀⠀⠀⠀⠀⢱⠀⠀⢰⠁⠀⠀⠀⠀⡇⠀⠀⠀⡸⠀⠀⠀⠀⠀⠀|
|⠱⡀⠀⠀⠀⠀⡸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡀⠀⡜⠀⠀⠀⠀⠀⢱⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀|
|⠀⢱⠀⠀⠀⢀⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⢀⠇⠀⠀⠀⠀⠀⠸⡀⠀⢸⠀⠀⠀⠀⠀⠀⠀|
|⠀⠀⢣⠀⠀⡸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⢸⠀⠀⠀⠀⠀⠀⠀⡇⠀⡇⠀⠀⠀⠀⠀⠀⠀|
|⠀⠀⠀⢇⢀⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡎⠀⠀⠀⠀⠀⠀⠀⢱⢰⠁⠀⠀⠀⠀⠀⠀⠀|
|⠀⠀⠀⠈⡾⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡎⠀⠀⠀⠀⠀⠀⠀⠀|
100 |⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀|
+-------------------------------------------------+
⠀1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀10⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀x⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

You can easily combine plots by using the mutating version for some methods. For example, we can add a new data set to our previous line plot as shown below:

Notice that I use a screenshot here to show the colors, which are otherwise not visible when copying to a code block. That being said, isn’t it amazing that we can also copy our plots as if they are text?

Some more examples:

Histogram with binning
Heatmap with “plasma” colormap

The plots have a surprisingly large number of customization options. Do take a look at the excellent set of examples provided in the package repository.⠀

Visualizing real-world data

Let’s face it, we have only looked at examples of dummy data up until now. Can these plots be also useful when dealing with realistic data sets? To find this out, I implemented support for Unicode plots in BitcoinRPC.jl, where we can use them to visualize blockchain data. Let’s look at some examples.

Average fee

I would like to see the historical daily average fee in the block for a duration of 12 weeks. This is conveniently implemented in the following function:

We can use our existing DataFrames as an input to lineplot along with several customizable keywords. Note that we make use of BrailleCanvas which provides the highest resolution for Unicode plotting. There are also others, e.g. BlockCanvas, AsciiCanvas etc.

Impressive, isn’t it? What can we learn from this? The average fee seems to be higher over the last couple of weeks. This usually happens when markets react to large price fluctuations. More people start moving coins to and fro between exchanges or other peers, resulting in the network becoming congested. To prioritize your transaction, you therefore end up paying a higher fee.

Number of transactions

Let’s check how the daily number looks like for a period of 12 weeks.

Total block output

We can also query for the total amount of Bitcoins in the outputs of a block, summed over daily intervals. See example for last 12 weeks:

Network hashrate

An important metric for any proof-of-work based blockchain is the network hashrate. It measures the collective mining power currently being used to mine new blocks. The hashrate can increase or decrease, depending on how many miners join or leave the network. For example when Bitcoin price goes up, and mining becomes more profitable, hashrate also tends to increase. The opposite might happen when there is a market downturn.

Network hashrate for last 48 weeks (~12 months)

Looking at the last 12 months, network hashrate seems to be steadily increasing as mining seems to remain profitable.

Network difficulty

The Bitcoin network adjusts its difficulty such that the time interval between successive blocks remains on an average ~10 minutes. It’s directly correlated to the network hashrate. Since the hashrate is going up, the difficulty also readjusts to a higher value.

Network difficulty for last 24 weeks (~6 months)

Block times

Using block timestamps, we can calculate the time elapsed between consecutive blocks. The Bitcoin network targets a block generation time of ~ 10 minutes. Blocks can be generated faster (< 10 mins) when the network hashrate increases significantly, whereas difficulty is still readjusting to a higher value. On the contrary, a decrease in the network hashrate will consequently increase block generation time (> 10 mins), until the difficulty readjusts to a lower value. It would be interesting to check how the distribution of block times would look like.

Histogram for block times over last 24 weeks (~ 6 months)

As expected, majority of blocks fall within the 0–10 mins window. A good number of them also show higher block times (20–40 mins). These likely correspond to times when network hashrate goes down, while difficulty is still high.

Conclusion

I must say that I am very impressed with how the plots have turned out. It’s important to remember that plotting in the terminal has its own limitations, and is by no means a substitute for advanced graphical libraries. It’s merely a simpler option, best used to get a quick visual insight into your data, without having to deal with the overhead of big plotting packages. UnicodePlots.jl also provides a lot of options for customization, so a variety of use cases are supported. I am excited to see how this package would evolve in the future. I hope you enjoyed reading this article. Thank you for your time, and hope to see you in the next one! In case you want to connect, here’s my LinkedIn.

--

--

Vikas Negi
Geek Culture

Senior Data Scientist at AkzoNobel • PhD in Applied Physics