Plots in the terminal (with sixel)

Does Sixel depend on the plotting library or the terminal itself or both? Does the WIndows Terminal support sixel? Does Kitty terminal support Sixel?

1 Like

Normally it should work like this:

GKSwstype=iterm julia
julia> using GR
julia> surface(peaks())

This works both with GR (see Retina screenshot) and Plots, but in Plots the resolution is extremely poor!?

Plots also supports an experimental sixel mode when ImageInTerminal is loaded:

julia> ENV["PLOTS_IMAGE_IN_TERMINAL"] = true;  # experimental for now
julia> using ImageInTerminal, Plots
julia> pyplot();  # or whatever backend that can save to `png` (except `UnicodePlots` which is built in for terminal plots)
julia> plot(1:2)

Here on mlterm, linux.

1 Like

Does this work in windows terminal?

I do not know that.
The not exhaustive list of tested terminals, which support sixels, are listed in GitHub - JuliaIO/Sixel.jl: The Julia wrapper of libsixel.

Exactly, that’s where default(display_type=:inline) shines.

Using GR.jl or GRUtils.jl rather than Plots.jl reduces the TTFP :smiley: but there are downsides for my setup (macOS, iTerm):

  1. GR.jl plots are always positioned at the upper left corner of the terminal window and mess up the scroll back.
  2. Plots.jl allows switching back and forth between default(display_type=:inline) → terminal and default(display_type=:gui) → GKSQT. No need to set an ENV variable before loading the plotting package.
  3. Additionally, GR.jl can’t consume Date/Time/DateTime vectors for the x axis out of the box.

Do you have any idea why GKSQT is launched when plotting inline? Is that a Plots.jl issue?

There used to be a GKSTerm.app that kept the plot history so you could go back. Is that app still around?

Thank you. I tried that but just got pages of errors instead of a figure (macOS 12.6.1, iTerm 3.4.17, Julia 1.8.3, Plots.jl 1.36.1 with GR backend, and a fresh ImageInTerminal.jl, everything’s up-to-date).

Why would I need ImageInTerminal.jl if there’s default(display_type=:inline)? Is it intended for Linux or Windows? It pulls in many additional dependencies and adds to TTFP.

EDIT: mentioned GR backend

What error ?

Why would I need ImageInTerminal.jl if there’s default(display_type=:inline)?

Not all backends support this: :inline is specific to currently only used by GR.
By using ImageInTerminal, we first encode a png (a feature that all backends support now), encode it in sixels and display them in the terminal: this is a backend independent approach.

Here on linux + mlterm, the ImageInTerminal approach works flawlessly with the GR backend.

Got it :+1:

Error resolved: now I see there’s one more package missing. I added ImageIO.jl manually and then it works as expected.

Just for the record:

I started with a fresh environment:

Status `~/.julia/environments/v1.8/Project.toml`
  [d8c32880] ImageInTerminal v0.5.2
  [91a5bcdd] Plots v1.36.1

Did:

ENV["PLOTS_IMAGE_IN_TERMINAL"] = true
using ImageInTerminal, Plots
plot(1:2)

Got:

Errors encountered while load FileIO.Stream{FileIO.DataFormat{:PNG}, IOBuffer, Nothing}(IOBuffer(data=UInt8[...], readable=true, writable=true, seekable=false, append=true, size=12904, maxsize=Inf, ptr=1, mark=-1), nothing).
All errors:
===========================================
ArgumentError: Package ImageIO [82e4d734-157c-48bb-816b-45c225c6df19] is required but does not seem to be installed:
 - Run `Pkg.instantiate()` to install all recorded dependencies.

===========================================
ArgumentError: Package QuartzImageIO [dca85d43-d64c-5e67-8c65-017450d5d020] is required but does not seem to be installed:
 - Run `Pkg.instantiate()` to install all recorded dependencies.

===========================================
ArgumentError: Package ImageMagick [6218d12a-5da1-5696-b52f-db25d2ecc6d1] is required but does not seem to be installed:
 - Run `Pkg.instantiate()` to install all recorded dependencies.

===========================================

Fatal error:
Error showing value of type Plots.Plot{Plots.GRBackend}:
ERROR: ArgumentError: Package ImageIO [82e4d734-157c-48bb-816b-45c225c6df19] is required but does not seem to be installed:
 - Run `Pkg.instantiate()` to install all recorded dependencies.

Stacktrace:
  [1] _require(pkg::Base.PkgId)
    @ Base ./loading.jl:1306
  [2] _require_prelocked(uuidkey::Base.PkgId)
    @ Base ./loading.jl:1200
  [3] macro expansion
    @ ./lock.jl:223 [inlined]
  [4] require(uuidkey::Base.PkgId)
    @ Base ./loading.jl:1195
  [5] #34
    @ ~/.julia/packages/FileIO/aP78L/src/loadsave.jl:203 [inlined]
  [6] lock(f::FileIO.var"#34#35"{Base.PkgId}, l::ReentrantLock)
    @ Base ./lock.jl:185
  [7] action(::Symbol, ::Vector{Union{Base.PkgId, Module}}, ::FileIO.Formatted; options::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ FileIO ~/.julia/packages/FileIO/aP78L/src/loadsave.jl:203
  [8] action
    @ ~/.julia/packages/FileIO/aP78L/src/loadsave.jl:196 [inlined]
  [9] #load#15
    @ ~/.julia/packages/FileIO/aP78L/src/loadsave.jl:120 [inlined]
 [10] load
    @ ~/.julia/packages/FileIO/aP78L/src/loadsave.jl:116 [inlined]
 [11] display(d::ImageInTerminal.TerminalGraphicDisplay{Base.TTY, Base.TTY}, #unused#::MIME{Symbol("image/png")}, bytes::Vector{UInt8})
    @ ImageInTerminal ~/.julia/packages/ImageInTerminal/VHb5T/src/display.jl:11
 [12] display(#unused#::Plots.PlotsDisplay, plt::Plots.Plot{Plots.GRBackend})
    @ Plots ~/.julia/packages/Plots/gzYVM/src/init.jl:131
 [13] display(x::Any)
    @ Base.Multimedia ./multimedia.jl:328
 [14] #invokelatest#2
    @ ./essentials.jl:729 [inlined]
 [15] invokelatest
    @ ./essentials.jl:726 [inlined]
 [16] print_response(errio::IO, response::Any, show_value::Bool, have_color::Bool, specialdisplay::Union{Nothing, AbstractDisplay})
    @ REPL /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/REPL.jl:296
 [17] (::REPL.var"#45#46"{REPL.LineEditREPL, Pair{Any, Bool}, Bool, Bool})(io::Any)
    @ REPL /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/REPL.jl:278
 [18] with_repl_linfo(f::Any, repl::REPL.LineEditREPL)
    @ REPL /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/REPL.jl:521
 [19] print_response(repl::REPL.AbstractREPL, response::Any, show_value::Bool, have_color::Bool)
    @ REPL /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/REPL.jl:276
 [20] (::REPL.var"#do_respond#66"{Bool, Bool, REPL.var"#77#87"{REPL.LineEditREPL, REPL.REPLHistoryProvider}, REPL.LineEditREPL, REPL.LineEdit.Prompt})(s::REPL.LineEdit.MIState, buf::Any, ok::Bool)
    @ REPL /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/REPL.jl:857
 [21] #invokelatest#2
    @ ./essentials.jl:729 [inlined]
 [22] invokelatest
    @ ./essentials.jl:726 [inlined]
 [23] run_interface(terminal::REPL.Terminals.TextTerminal, m::REPL.LineEdit.ModalInterface, s::REPL.LineEdit.MIState)
    @ REPL.LineEdit /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/LineEdit.jl:2510
 [24] run_frontend(repl::REPL.LineEditREPL, backend::REPL.REPLBackendRef)
    @ REPL /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/REPL.jl:1248
 [25] (::REPL.var"#49#54"{REPL.LineEditREPL, REPL.REPLBackendRef})()
    @ REPL ./task.jl:484
Stacktrace:
  [1] handle_error(e::ArgumentError, q::Base.PkgId, bt::Vector{Union{Ptr{Nothing}, Base.InterpreterIP}})
    @ FileIO ~/.julia/packages/FileIO/aP78L/src/error_handling.jl:61
  [2] handle_exceptions(exceptions::Vector{Tuple{Any, Union{Base.PkgId, Module}, Vector}}, action::String)
    @ FileIO ~/.julia/packages/FileIO/aP78L/src/error_handling.jl:56
  [3] action(::Symbol, ::Vector{Union{Base.PkgId, Module}}, ::FileIO.Formatted; options::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ FileIO ~/.julia/packages/FileIO/aP78L/src/loadsave.jl:228
  [4] action
    @ ~/.julia/packages/FileIO/aP78L/src/loadsave.jl:196 [inlined]
  [5] #load#15
    @ ~/.julia/packages/FileIO/aP78L/src/loadsave.jl:120 [inlined]
  [6] load
    @ ~/.julia/packages/FileIO/aP78L/src/loadsave.jl:116 [inlined]
  [7] display(d::ImageInTerminal.TerminalGraphicDisplay{Base.TTY, Base.TTY}, #unused#::MIME{Symbol("image/png")}, bytes::Vector{UInt8})
    @ ImageInTerminal ~/.julia/packages/ImageInTerminal/VHb5T/src/display.jl:11
  [8] display(#unused#::Plots.PlotsDisplay, plt::Plots.Plot{Plots.GRBackend})
    @ Plots ~/.julia/packages/Plots/gzYVM/src/init.jl:131
  [9] display(x::Any)
    @ Base.Multimedia ./multimedia.jl:328
 [10] #invokelatest#2
    @ ./essentials.jl:729 [inlined]
 [11] invokelatest
    @ ./essentials.jl:726 [inlined]
 [12] print_response(errio::IO, response::Any, show_value::Bool, have_color::Bool, specialdisplay::Union{Nothing, AbstractDisplay})
    @ REPL /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/REPL.jl:296
 [13] (::REPL.var"#45#46"{REPL.LineEditREPL, Pair{Any, Bool}, Bool, Bool})(io::Any)
    @ REPL /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/REPL.jl:278
 [14] with_repl_linfo(f::Any, repl::REPL.LineEditREPL)
    @ REPL /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/REPL.jl:521
 [15] print_response(repl::REPL.AbstractREPL, response::Any, show_value::Bool, have_color::Bool)
    @ REPL /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/REPL.jl:276
 [16] (::REPL.var"#do_respond#66"{Bool, Bool, REPL.var"#77#87"{REPL.LineEditREPL, REPL.REPLHistoryProvider}, REPL.LineEditREPL, REPL.LineEdit.Prompt})(s::REPL.LineEdit.MIState, buf::Any, ok::Bool)
    @ REPL /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/REPL.jl:857
 [17] #invokelatest#2
    @ ./essentials.jl:729 [inlined]
 [18] invokelatest
    @ ./essentials.jl:726 [inlined]
 [19] run_interface(terminal::REPL.Terminals.TextTerminal, m::REPL.LineEdit.ModalInterface, s::REPL.LineEdit.MIState)
    @ REPL.LineEdit /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/LineEdit.jl:2510
 [20] run_frontend(repl::REPL.LineEditREPL, backend::REPL.REPLBackendRef)
    @ REPL /Applications/julia-1.8.3/share/julia/stdlib/v1.8/REPL/src/REPL.jl:1248
 [21] (::REPL.var"#49#54"{REPL.LineEditREPL, REPL.REPLBackendRef})()
    @ REPL ./task.jl:484

Okay, I see there’s one more package missing. I added ImageIO.jl

1 Like

Nice that you got it working: optional dependencies are a bit of a pain in julia (but they are being worked on: Support in code loading and precompilation for weak dependencies by KristofferC · Pull Request #47040 · JuliaLang/julia · GitHub).

1 Like
  1. using GR; inline("item", false) allows scroll back
  2. inline("") switches back to GUI
  3. NYI

For Plots, launching of gksqt might be suppressed setting GKSwstype=nul before starting Julia.

GKSTerm.app for macOS is still there:

GKSwstype=quartz julia ...
1 Like

That does the trick :+1:

Regarding sixel output, the gnuplot program and thus
Gnuplot.jl and Gaston.jl support that output format.

xterm can display sixel graphics if compiled with the
appropriate flags ( --enable-sixel-graphics ).

NOTE: Removed outdated libsixel information.

Please see Plots in the terminal (with sixel) - #34 by t-bltg
and thanks to t-bltg for the correction!

1 Like

Note that saitoha/libsixel is unmaintained and has been forked at libsixel/libsixel (see issue).

1 Like

As an alternative to xterm, wezterm is a modern terminal that supports sixel. It has a few rough edges but it’s in active development. kitty doesn’t support sixel, but there is GitHub - simonschoelly/KittyTerminalImages.jl: A package that allows Julia to display images in the kitty terminal editor

2 Likes

I just discovered a nice trick with sixel and gnuplot: in-terminal animations. The following code is from Gaston’s development branch (still not published), but should be easy to adapt to Gaston current or to Gnuplot.jl. Note the animate in the term definition.

using Gaston

Gaston.config.term = "sixelgd truecolor size 320, 200 animate"
f = Figure()
phase = 0:0.05:2π
for i in range(0, 2π, length=40)
    plot(f, sin.(phase.+i)) |> display
    sleep(0.1)
end

Peek2022-11-17-10-25

Not very practical since it messes up the terminal, but cool anyway.

4 Likes

would be cleaner if it can be put at the upper right corner of the term :stuck_out_tongue_winking_eye:

1 Like

Yeah – I wonder if Gnuplot’s developers would be open to feature requests, now that interest in sixel is increasing and more terminals support it.

1 Like

This is amazing. I only have one problem.
My terminal background is black. The text, frame, and ticks, are all black. Is it possible to change this, allowing plotting on dark backgrounds? The current look is like this:

I have tried mucking around with gr3.setbackgroundcolor, giving arguments like all zeros, all 256, all 0.5, but nothing produced a visible change.

In dark mode you have to add GR.usecolorscheme() after importing GR:

using GR
usecolorscheme(2)
...
2 Likes