Tips for displaying plots in terminals with varying support

Usually I use a notebook or save plots to images to open in an another application, but given that the practicality of AOT-compiled executables could improve drastically soon, I started looking into displaying a practical variety of plots in the terminal without switching to another window. So far I’m considering UnicodePlots.jl, which does pretty much what the name suggests (though it has some colors on Windows Powershell that doesn’t show up here):

PS C:\Users\Benny> julia -e 'using UnicodePlots; x=0:0.5:10; p = lineplot(x, sin.(x), height=:auto, width=:auto); scatterplot!(p, x, sin.(x), marker=:circle) |> display'
      ┌───────────────────────────────────┐
    1 │⠀⠀⠀⚬⠔⚬⠑⚬⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⚬⠉⚬⚬⠀⠀⠀⠀⠀│
      │⠀⠀⡰⠁⠀⠀⠀⠈⚬⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⚬⠁⠀⠀⠀⠀⢣⠀⠀⠀⠀│
      │⠀⚬⠁⠀⠀⠀⠀⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠁⠀⠀⠀⠀⠀⠀⚬⠀⠀⠀│
      │⢠⠃⠀⠀⠀⠀⠀⠀⠀⠈⚬⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⚬⠁⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀⠀│
      │⚬⠤⠤⠤⠤⠤⠤⠤⠤⠤⠼⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤⠧⠤⠤⠤⠤⠤⠤⠤⠤⠤⠼⚬⠤│
      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⢀⚬⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⡀│
      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⚬⠀⠀⠀⠀⠀⠀⢀⠎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⚬│
      │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢣⚬⠀⠀⠀⠀⚬⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
   -1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⚬⣀⚬⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
      └───────────────────────────────────┘
      ⠀0⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀10⠀
PS C:\Users\Benny>

Definitely doesn’t look as good as an image with finer pixels, but the docs also suggests Sixel (related: ImageinTerminal.jl, Sixel.jl, libsixel) for that. Thing is, it’s easy to run into terminals that don’t support Sixel, and that also got me wondering if it’s possible to run into terminals that don’t support Unicode either (maybe just ASCII).

  1. Is there a way to identify the terminal from Julia in order to fall back to UnicodePlots if Sixel is not supported?
  2. If it is at all possible to run into an ASCII-only terminal, would Julia code containing non-ASCII characters parse properly even if UnicodePlots would only display ASCII? Would Julia code intentionally restricted to ASCII?
  3. How practical is it to even bother with terminal plots? It’s not actually much trouble to open an application for viewing standard image files in a separate window and to close it, even without a mouse, and I’m unlikely to run into a platform at this point in time where that isn’t an option, let alone choose such a platform for graphics.
1 Like

Some more recent terminal emulators like Kitty or Ghostty don’t support Sixel but the Kitty Terminal graphics protocol. You may want to account for that too (see KittyTerminalImages.jl).

As for your questions:

  1. You could check ENV["TERM"] and match a list of known terminal names but that’ll get tedious if you want a portable solution. Alternatively, query the terminal. I don’t have a ready-to-use function for your needs but it would look similar to retrieving the terminal window size (this code is copied from my own, unregistered GhosttyExtensions.jl):
using REPL
function pixelsize()
    term = REPL.Terminals.TTYTerminal("xterm", stdin, stdout, stderr)
    REPL.Terminals.raw!(term, true)
    Base.start_reading(stdin)
    print(stdout, "\e[14t") # send an escape sequence to ask for the terminal's capabilities
    data = readuntil(stdin, "t") # read the terminal's response
    startswith(data, "\e[4;") || return (0, 0) # parse the terminal's response
    height, width = split(chopprefix(data, "\e[4;"), ';')
    return parse(Int, width), parse(Int, height)
end
  1. Depends on your environment. I haven’t touched a pure ASCII terminal in three decades :smiley:.

  2. Depends on your environment too:

  • Plotting inside the terminal works over ssh.
  • You can scroll back and see the history of your session (the code and the resulting plots).
  • In my experience, the REPL in a terminal is much more robust than say, the integration in VSCode.
  • Some people just prefer to use terminals as much as possible.

It will be tricky to support all terminals and setups out there. I would start with my personal setup and add more things later if necessarily.

2 Likes

You can use Sixel.is_sixel_supported()

using Sixel
Sixel.is_sixel_supported()

and, if true, then

ENV["GKSwstype"]="nul"
using Plots
using SixelTerm
3 Likes

Since I found how well VSCode can handle editing code in remote servers, I stopped searching for that. Currently when I need such graphics in a headless node, I just use VSCode to connect to it and have the plots displayed in the VSCode pane, as if I was coding locally. I’m not sure if this helps you specifically, but I guess it can be helpful for many others trying to run analysis of data on remote machines.

4 Likes

Yeah I didn’t really think that far, I really just hoped I can just write one command and immediately see a graphic for very simple plotting scripts that can be soon more easily compiled ahead-of-time. I really didn’t want terminal dependence on top of platform dependence, so I was hoping there woud be a simple branch I could do without increasing the source or compiled code much. Given how widely available applications for displaying standard image files are (and with even more commented approaches I didn’t even anticipate), there’s only so much effort that’s worth putting into one terminal plot per command.

I don’t even really know at this point when UnicodePlots alone could be preferable to other plotting libraries saving or rendering better images for bigger applications. It’s a bit big of a dependency for very simple REPL printouts like the very short histograms (the name for the style escapes me at the moment) from BenchmarkTools.@benchmark, and I can’t really imagine when I would prefer to save a plot as Unicode text instead of input data for a script or notebook, or embed it into a document instead of an image.

1 Like