# \[ANN\] UnicodePlots.jl v2.8.0 - 3D plots

**URL:** <https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963>\
**Category:** Package Announcements\
**Tags:** repl, plots, unicodeplots\
**Created:** [February 7, 2022, 6:25pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963 "2022-02-07T18:25:08Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![t-bltg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/t-bltg/32/25526_2.png) [@t-bltg](https://discourse.julialang.org/u/t-bltg)\
**Post date:** [February 7, 2022, 6:25pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/1 "2022-02-07T18:25:08Z")

</div>

Announcing [`UnicodePlots`](https://github.com/JuliaPlots/UnicodePlots.jl) v2.8.0, which brings up 3D plots.

Examples:

[`surfaceplot`](https://github.com/JuliaPlots/UnicodePlots.jl#surface-plot)

 ![surfaceplot1](https://global.discourse-cdn.com/julialang/original/3X/d/8/d835f59760f6e343b6f9f72ee2de2b0aa7ba845c.png)  
 ![surfaceplot2](https://global.discourse-cdn.com/julialang/original/3X/5/9/59ec35f676b4b801a8c1fcc90de53c42d5c953ab.png)

[`isosurface`](https://github.com/JuliaPlots/UnicodePlots.jl#isosurface-plot)

 ![isosurface](https://global.discourse-cdn.com/julialang/original/3X/4/9/493e185c6881f5b8c96ba79e007d66570665852d.png)

Useful for in situ post-processing of simulations, or as a development helper for 3D codes.

In case you missed out, [`contourplot`](https://github.com/JuliaPlots/UnicodePlots.jl#contour-plot) was also recently added.

---

<div class="post-metadata">

**Author:** ![davidanthoff](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/davidanthoff/32/223493_2.png) [@davidanthoff](https://discourse.julialang.org/u/davidanthoff)\
**Post date:** [February 7, 2022, 6:29pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/2 "2022-02-07T18:29:16Z")

</div>

This is crazy and amazing! 🙂

---

<div class="post-metadata">

**Author:** ![mtfishman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mtfishman/32/30755_2.png) [@mtfishman](https://discourse.julialang.org/u/mtfishman)\
**Post date:** [February 7, 2022, 7:21pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/3 "2022-02-07T19:21:29Z")

</div>

Very cool! Any chance there might be 3D scatter plots? We would have some applications for that, though they might be difficult for human consumption in general.

---

<div class="post-metadata">

**Author:** ![t-bltg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/t-bltg/32/25526_2.png) [@t-bltg](https://discourse.julialang.org/u/t-bltg)\
**Post date:** [February 7, 2022, 7:28pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/4 "2022-02-07T19:28:56Z")

</div>

Yes, I’ve modified `scatterplot` and `lineplot` to take `z` values, so you can use something like:

```julia
scatterplot(
  x, y, z;
  projection = :orthographic # or :perspective, or a full `MVP` matrix.
)

```

Internally, `surfaceplot` uses `points!` unless kw `lines = true` is passed (which is slower and more dense).

---

<div class="post-metadata">

**Author:** ![oheil](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oheil/32/220745_2.png) [@oheil](https://discourse.julialang.org/u/oheil)\
**Post date:** [February 7, 2022, 8:32pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/5 "2022-02-07T20:32:03Z")

</div>

Inconceivable!

---

<div class="post-metadata">

**Author:** ![AlexisRenchon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/alexisrenchon/32/13328_2.png) [@AlexisRenchon](https://discourse.julialang.org/u/AlexisRenchon)\
**Post date:** [February 8, 2022, 5:29pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/6 "2022-02-08T17:29:20Z")

</div>

A dream come true!  
Now we need interactivity 😇

---

<div class="post-metadata">

**Author:** ![e3c6](https://avatars.discourse-cdn.com/v4/letter/e/e79b87/32.png) [@e3c6](https://discourse.julialang.org/u/e3c6)\
**Post date:** [February 8, 2022, 5:58pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/7 "2022-02-08T17:58:06Z")

</div>

Amazing.

Is there any support for animations?

---

<div class="post-metadata">

**Author:** ![t-bltg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/t-bltg/32/25526_2.png) [@t-bltg](https://discourse.julialang.org/u/t-bltg)\
**Post date:** [February 8, 2022, 6:07pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/8 "2022-02-08T18:07:33Z")

</div>

> [@AlexisRenchon](#):
>
> Now we need interactivity 😇

There is [Makie.jl](https://github.com/JuliaPlots/Makie.jl) for that, and for animations 🙂 I don’t think this should be the target of `UnicodePlots` (which is intended to be a lightweight swiss army knife with very few dependencies, that just works right out of the box).

EDIT: after discussion with @AlexisRenchon on slack, we can maybe make animations work from within [`Plots.jl`](https://docs.juliaplots.org/latest/animations/), since `UnicodePlots` is a supported [backend](https://docs.juliaplots.org/latest/generated/unicodeplots/).

---

<div class="post-metadata">

**Author:** ![DoktorMike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/doktormike/32/2736_2.png) [@DoktorMike](https://discourse.julialang.org/u/DoktorMike)\
**Post date:** [February 8, 2022, 6:34pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/9 "2022-02-08T18:34:15Z")

</div>

This is fantastic! I loved this package before, but now there is simply no Julia for me without UnicodePlots. 💪🏻😊🙏🏻

---

<div class="post-metadata">

**Author:** ![TI36XPro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ti36xpro/32/33658_2.png) [@TI36XPro](https://discourse.julialang.org/u/TI36XPro)\
**Post date:** [February 8, 2022, 9:22pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/10 "2022-02-08T21:22:43Z")

</div>

How cool - I definitely want to try to find a way to work it into my current work/packages 🙂

---

<div class="post-metadata">

**Author:** ![t-bltg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/t-bltg/32/25526_2.png) [@t-bltg](https://discourse.julialang.org/u/t-bltg)\
**Post date:** [February 8, 2022, 9:45pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/11 "2022-02-08T21:45:13Z")

</div>

```julia
using Plots; unicodeplots() # using `Plots@master`, with linux ;), maybe works on macOS

main() = begin
  anim = @animate for i = -180:10:180
    surface(
      -8:.5:8, -8:.5:8, (x, y) -> 15sinc(√(x^2 + y^2) / π),
      colormap=:jet, camera=(i, 30)
    )
  end
  gif(anim, "anim_fps5.gif", fps=5)
end

main()

```

![anim_fps5](https://global.discourse-cdn.com/julialang/original/3X/a/7/a73b2039415a3e8d8dbb781c3aa45090558adaca.gif)

```julia
using Plots; unicodeplots()

main() = begin
  Plots.UnicodePlots.default_size!(width=120)
  anim = @animate for i ∈ -180:10:180
    surface(
      -8:8, -8:8, (x, y) -> 15sinc(√(x^2 + y^2) / π),
      camera=(i, 30),
      extra_kwargs = Dict(
        :subplot => (zoom=1.25,),
        :series =>(colormap=:jet, lines=true)
      )
    )
  end
  gif(anim, "anim_lines_fps5.gif", fps=5)
end

main()

```

![anim_lines_fps5](https://global.discourse-cdn.com/julialang/original/3X/e/0/e0ed974cb683dc58d3150990bf4cf598dec24d42.gif)

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [February 8, 2022, 11:40pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/12 "2022-02-08T23:40:13Z")

</div>

> [@t-bltg](#):
>
> we can maybe make animations work from within [`Plots.jl`](https://docs.juliaplots.org/latest/animations/), since `UnicodePlots` is a supported [backend](https://docs.juliaplots.org/latest/generated/unicodeplots/)

and it may be the only backend that supports multi-threading animation (making independent frames in parallel) because all other backends are stateful as hell

---

<div class="post-metadata">

**Author:** ![yakir12](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yakir12/32/297_2.png) [@yakir12](https://discourse.julialang.org/u/yakir12)\
**Post date:** [February 28, 2022, 9:57am UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/13 "2022-02-28T09:57:11Z")

</div>

I just wanted to report back here that I managed to get relatively good “animation” just by clearing the REPL before plotting (Thanks to @Tamas_Papp [here](https://discourse.julialang.org/t/how-clear-the-printed-content-in-terminal-and-print-to-the-same-line/19549/9)). This allows for live plotting (i.e. closer to using observables like in Makie).

MWE:

```julia
using UnicodePlots
import REPL
terminal = REPL.Terminals.TTYTerminal("", stdin, stdout, stderr)

xs = range(0, 7, length=40)
fps = 30
for t in 0:1/fps:100
  ys = sin.(xs .- t)
  REPL.Terminals.clear(terminal)
  println(lineplot(xs, ys, xlim = (0, 7), ylim = (-1, 1)))
  sleep(1/fps)
end

```

![animation](https://global.discourse-cdn.com/julialang/original/3X/c/e/ce927cc8311958ba5749d56b22b380302d4c63dc.webp)

It’s a bit jumpy, but saves the day on diagnosing a headless server without XForwarding…

---

<div class="post-metadata">

**Author:** ![Raf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/raf/32/3383_2.png) [@Raf](https://discourse.julialang.org/u/Raf)\
**Post date:** [February 28, 2022, 10:13am UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/14 "2022-02-28T10:13:32Z")

</div>

It helps if you hide the cursor 🙂

[https://github.com/cesaraustralia/DynamicGrids.jl/blob/master/src/outputs/repl.jl#L55-L71](https://github.com/cesaraustralia/DynamicGrids.jl/blob/master/src/outputs/repl.jl#L55-L71)

DynamicGrids.jl has repl simulations and they’re pretty smooth using these tricks

---

<div class="post-metadata">

**Author:** ![yakir12](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yakir12/32/297_2.png) [@yakir12](https://discourse.julialang.org/u/yakir12)\
**Post date:** [February 28, 2022, 10:37am UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/15 "2022-02-28T10:37:16Z")

</div>

Right you are:  
 ![animation](https://global.discourse-cdn.com/julialang/original/3X/5/d/5d3ff93f2f640b3fe549e8fb9d0f166c13cd64d5.webp)

Still some flickering…

---

<div class="post-metadata">

**Author:** ![t-bltg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/t-bltg/32/25526_2.png) [@t-bltg](https://discourse.julialang.org/u/t-bltg)\
**Post date:** [February 28, 2022, 2:11pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/16 "2022-02-28T14:11:35Z")

</div>

Thanks for sharing this !

> Still some flickering…

You can move the `show` logic before clearing the screen:

```julia
using UnicodePlots
import REPL

_cursor_hide(io::IO) = print(io, "\x1b[?25l")
_cursor_show(io::IO) = print(io, "\x1b[?25h")

main() = begin
  terminal = REPL.Terminals.TTYTerminal("", stdin, stdout, stderr)

  fps = 30
  xs = range(0, 7, length=40)
  _cursor_hide(stdout)
  io = IOContext(PipeBuffer(), :color=>true)
  for t ∈ 0:(1 / fps):100
    ys = sin.(xs .- t)
    show(io, lineplot(xs, ys, xlim=(0, 7), ylim=(-1, 1)))
    out = read(io, String)
    REPL.Terminals.clear(terminal)
    println(out)
    sleep(1 / fps)
  end
  _cursor_show(stdout)
  return
end

main()

```

---

<div class="post-metadata">

**Author:** ![yakir12](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yakir12/32/297_2.png) [@yakir12](https://discourse.julialang.org/u/yakir12)\
**Post date:** [February 28, 2022, 2:23pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/17 "2022-02-28T14:23:39Z")

</div>

![animation](https://global.discourse-cdn.com/julialang/original/3X/5/f/5ffcd816ecdb3f7da2fec454b28c105977a7e1d9.webp)  
Better…

---

<div class="post-metadata">

**Author:** ![johnh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/johnh/32/3615_2.png) [@johnh](https://discourse.julialang.org/u/johnh)\
**Post date:** [February 28, 2022, 2:25pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/18 "2022-02-28T14:25:17Z")

</div>

Ooooh… when I were lad I used the CERN HBOOK package to plot histograms on line printers using music ruled paper.  
I guess I can still do the same - if I Can find a line printer!  
Seriously - the 3D stuff is coooollll

---

<div class="post-metadata">

**Author:** ![tbeason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tbeason/32/15898_2.png) [@tbeason](https://discourse.julialang.org/u/tbeason)\
**Post date:** [February 28, 2022, 2:31pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/19 "2022-02-28T14:31:01Z")

</div>

Stupid (in a good way) that unicode plotting in the REPL is almost at parity with other plotting libraries in Julia haha

---

<div class="post-metadata">

**Author:** ![mtfishman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mtfishman/32/30755_2.png) [@mtfishman](https://discourse.julialang.org/u/mtfishman)\
**Post date:** [October 15, 2025, 2:48pm UTC](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963/20 "2025-10-15T14:48:26Z")

</div>

Apologies for bringing this thread back to life, but here is a slightly refactored version of the script @t-bltg wrote above in terms of a function which outputs the plot at each frame:

```julia
"""
    animate(f; nframes, fps=30)

Call `f(i)` for each frame `i in 1:nframes`, where `f(i)` returns an object to print to the
terminal at each frame. Renders each frame in-place in the terminal at `fps` frames per
second.
"""
function animate(f; nframes::Int, fps::Real=30)
    io = IOBuffer()
    # Hide cursor.
    print(stdout, "\x1b[?25l")
    try
        # Clear screen.
        print(stdout, "\x1b[2J")
        for i in 1:nframes
            seekstart(io)
            show(io, MIME("text/plain"), f(i))
            frame_str = String(take!(io))
            # Move home.
            print(stdout, "\x1b[H")
            println(frame_str)
            sleep(1 / fps)
        end
    finally
        # Show cursor again.
        print(stdout, "\x1b[?25h")
    end
end

using Plots: Plots, plot
Plots.unicodeplots()
xs = range(0, 7; length=40)
animate(; nframes=50, fps=10) do i
    return plot(xs, sin.(xs .- 0.2i); ylim=(-1,1))
end

```

(written with the help of ChatGPT.)

[Next page](https://discourse.julialang.org/t/ann-unicodeplots-jl-v2-8-0-3d-plots/75963.md?page=2)
