Advanced Unicode
plotting library designed for use in Julia
's REPL
.
Here is a list of the main high-level functions for common scenarios:
lineplot
(Line Plot)scatterplot
(Scatter Plot)stairs
(Staircase Plot)barplot
(Bar Plot - horizontal)histogram
(Histogram - horizontal / vertical)boxplot
(Box Plot - horizontal)spy
(Sparsity Pattern)densityplot
(Density Plot)contourplot
(Contour Plot)polarplot
(Polar Plot)heatmap
(Heatmap Plot)imageplot
(Image Plot)surfaceplot
(Surface Plot - 3D)isosurface
(Isosurface Plot - 3D)
Introduction
Here is a quick hello world example of a typical use-case:
using UnicodePlots
lineplot([-1, 2, 3, 7], [-1, 2, 9, 4], title="Example", name="my line", xlabel="x", ylabel="y")
There are other types of Canvas
available (see section Low-level Interface).
In some situations, such as printing to a file, using AsciiCanvas
, DotCanvas
or BlockCanvas
might lead to better results:
plt = lineplot([-1, 2, 3, 7], [-1, 2, 9, 4], title="Example", name="my line",
xlabel="x", ylabel="y", canvas=DotCanvas, border=:ascii)
Some plot methods have a mutating variant that ends with an exclamation mark:
lineplot!(plt, [0, 4, 8], [10, 1, 10], color=:cyan, name="other line")
These mutating methods cannot update the limits of the axes as plots are drawn onto a fixed canvas. The limits must be set beforehand by the plotting function that creates the figure or by creating an empty Plot
:
p = Plot(; xlim=(-1, 3), ylim=(-1, 3))
lineplot!(p, 1:2)
One can adjust the plot height
and width
to the current terminal size by using height=:auto
and/or width=:auto
.
You can reverse/flip the Plot
axes by setting xflip=true
and/or yflip=true
on plot creation.
Lineplot
lineplot([1, 2, 7], [9, -6, 8], title="My Lineplot")
It's also possible to specify a function and a range:
plt = lineplot(-π/2, 2π, [cos, sin])
You can also plot lines by specifying an intercept and slope:
lineplot!(plt, -.5, .2, name="line")
Plotting multiple series is supported by providing a Matrix
(<: AbstractMatrix
) for the y
argument, with the individual series corresponding to its columns. Auto-labeling is by default, but you can also label each series by providing a Vector
or a 1xn
Matrix
such as ["series 1" "series2" ...]
:
lineplot(1:10, [0:9 3:12 reverse(5:14) fill(4, 10)], color=[:green :red :yellow :cyan])
Physical quantities of Unitful.jl
are supported through package extensions - weak dependencies:
using Unitful
a, t = 1u"m/s^2", (0:100) * u"s"
lineplot(a / 2 * t .^ 2, a * t, xlabel="position", ylabel="speed", height=10)
Intervals from IntervalSets.jl
are supported:
using IntervalSets
lineplot(-1..3, x -> x^5 - 5x^4 + 5x^3 + 5x^2 - 6x - 1; name="quintic")
Use head_tail
to mimic plotting arrows (:head
, :tail
or :both
) where the length of the "arrow" head or tail is controlled using head_tail_frac
where e.g. giving a value of 0.1
means 10%
of the segment length:
lineplot(1:10, 1:10, head_tail=:head, head_tail_frac=.1, height=4)
UnicodePlots
exports hline!
and vline!
for drawing vertical and horizontal lines on a plot:
p = Plot([NaN], [NaN]; xlim=(0, 8), ylim=(0, 8))
vline!(p, [2, 6], [2, 6], color=:red)
hline!(p, [2, 6], [2, 6], color=:white)
hline!(p, 7, color=:cyan)
vline!(p, 1, color=:yellow)
Scatterplot
scatterplot(randn(50), randn(50), title="My Scatterplot")
Axis scaling (xscale
and/or yscale
) is supported: choose from (:identity
, :ln
, :log2
, :log10
) or use an arbitrary scale function:
scatterplot(1:10, 1:10, xscale=:log10, yscale=:log10)
For the axis scale exponent, one can revert to using ASCII
characters instead of Unicode
ones using the keyword unicode_exponent=false
:
scatterplot(1:4, 1:4, xscale=:log10, yscale=:ln, unicode_exponent=false, height=6)
Using a marker
is supported, choose a Char
, a unit length String
or a symbol name such as :circle
(more from keys(UnicodePlots.MARKERS)
).
One can also provide a vector of marker
s and/or color
s as in the following example:
scatterplot([1, 2, 3], [3, 4, 1], marker=[:circle, '', "∫"],
color=[:cyan, nothing, :yellow], height=2)
As with lineplot
, scatterplot
supports plotting physical Unitful
quantities, or plotting multiple series (Matrix
argument).
Staircase plot
stairs([1, 2, 4, 7, 8], [1, 3, 4, 2, 7],
color=:yellow, style=:post, height=6, title="Staircase")
Barplot
barplot(["Paris", "New York", "Madrid"], [2.244, 8.406, 3.165], title="Population")
Note: You can use the keyword argument symbols
to specify the characters that should be used to plot the bars (e.g. symbols=['#']
).
Histogram
histogram(randn(1_000) .* .1, nbins=15, closed=:left)
The histogram
function also supports axis scaling using the parameter xscale
:
histogram(randn(1_000) .* .1, nbins=15, closed=:right, xscale=:log10)
Vertical histograms are supported:
histogram(randn(100_000) .* .1, nbins=60, vertical=true, height=10)
Boxplot
boxplot([1, 3, 3, 4, 6, 10])
boxplot(["one", "two"],
[[1, 2, 3, 4, 5], [2, 3, 4, 5, 6, 7, 8, 9]],
title="Grouped Boxplot", xlabel="x")
Sparsity Pattern
using SparseArrays
spy(sprandn(50, 120, .05))
Plotting the zeros pattern is also possible using show_zeros=true
:
using SparseArrays
spy(sprandn(50, 120, .9), show_zeros=true)
Density Plot
plt = densityplot(randn(10_000), randn(10_000))
densityplot!(plt, randn(10_000) .+ 2, randn(10_000) .+ 2)
Using a scale function (e.g. damping peaks) is supported using the dscale
keyword:
x = randn(10_000); x[1_000:6_000] .= 2
densityplot(x, randn(10_000); dscale=x -> log(1 + x))
Contour Plot
contourplot(-3:.01:3, -7:.01:3, (x, y) -> exp(-(x / 2)^2 - ((y + 2) / 4)^2))
The keyword levels
controls the number of contour levels. One can also choose a colormap
as with heatmap
, and disable the colorbar using colorbar=false
.
Polar Plot
Plots data in polar coordinates with θ
the angles in radians.
polarplot(range(0, 2π, length=20), range(0, 2, length=20))
Heatmap Plot
heatmap(repeat(collect(0:10)', outer=(11, 1)), zlabel="z")
The heatmap
function also supports axis scaling using the parameters xfact
, yfact
and axis offsets after scaling using xoffset
and yoffset
.
The colormap
parameter may be used to specify a named or custom colormap. See the heatmap
function documentation for more details.
In addition, the colorbar
and colorbar_border
options may be used to toggle the colorbar and configure its border.
The zlabel
option and zlabel!
method may be used to set the z
axis (colorbar) label.
Use the array
keyword in order to display the matrix in the array convention (as in the repl).
heatmap(collect(0:30) * collect(0:30)', xfact=.1, yfact=.1, xoffset=-1.5, colormap=:inferno)
Image Plot
Draws an image, surround it with decorations. Sixel
are supported (experimental) under a compatible terminal through ImageInTerminal
(which must be imported before UnicodePlots
).
import ImageInTerminal # mandatory (triggers glue code loading)
using TestImages
imageplot(testimage("monarch_color_256"), title="monarch")
Surface Plot
Plots a colored surface using height values z
above a x-y
plane, in three dimensions (masking values using NaN
s is supported).
sombrero(x, y) = 15sinc(√(x^2 + y^2) / π)
surfaceplot(-8:.5:8, -8:.5:8, sombrero, colormap=:jet)
Use lines=true
to increase the density (underlying call to lineplot
instead of scatterplot
, with color interpolation).
By default, surfaceplot
scales heights to adjust aspect wrt the remaining axes with zscale=:aspect
.
To plot a slice in 3D, use an anonymous function which maps to a constant value: zscale=z -> a_constant
:
surfaceplot(
-2:2, -2:2, (x, y) -> 15sinc(√(x^2 + y^2) / π),
zscale=z -> 0, lines=true, colormap=:jet
)
Isosurface Plot
Uses MarchingCubes.jl
to extract an isosurface, where isovalue
controls the surface isovalue.
Using centroid
enables plotting the triangulation centroids instead of the triangle vertices (better for small plots).
Back face culling (hide not visible facets) can be activated using cull=true
.
One can use the legacy 'Marching Cubes' algorithm using legacy=true
.
torus(x, y, z, r=0.2, R=0.5) = (√(x^2 + y^2) - R)^2 + z^2 - r^2
isosurface(-1:.1:1, -1:.1:1, -1:.1:1, torus, cull=true, zoom=2, elevation=50)
...
To install UnicodePlots, start up Julia
and type the following code snippet into the REPL
(makes use of the native Julia
package manager Pkg
):
julia> using Pkg
julia> Pkg.add("UnicodePlots")
...
Saving plots as png
or txt
files using the savefig
command is supported (saving as png
is experimental and requires import FreeType, FileIO
before loading UnicodePlots
).
To recover the plot as a string with ansi color codes use string(p; color=true)
.
...
When the COLORTERM
environment variable is set to either 24bit
or truecolor
, UnicodePlots
will use 24bit colors as opposed to 8bit colors or even 4bit colors for named colors.
One can force a specific colormode using either UnicodePlots.truecolors!()
or UnicodePlots.colors256!()
.
Named colors such as :red
or :light_red
will use 256
color values (rendering will be terminal dependent). In order to force named colors to use true colors instead, use UnicodePlots.USE_LUT[]=true
.
The default color cycle can be changed to bright (high intensity) colors using UnicodePlots.brightcolors!()
instead of the default UnicodePlots.faintcolors!()
.
...
3d plots use a so-called "Model-View-Projection" transformation matrix MVP
on input data to project 3D plots to a 2D screen.
Use keywordselevation
, azimuth
, up
or zoom
to control the view matrix, a.k.a. camera.
The projection
type for MVP
can be set to either :persp(ective)
or :ortho(graphic)
.
Displaying the x
, y
, and z
axes can be controlled using the axes3d
keyword.
For enhanced resolution, use a wider and/or taller Plot
(this can be achieved using default_size!(width=60)
for all future plots).
...
UnicodePlots
is integrated in Plots
as a backend, with support for basic layout.
For a more complex layout, use the gridplot
function (requires loading Term
as extension).
using UnicodePlots, Term
(
UnicodePlots.panel(lineplot(1:2)) *
UnicodePlots.panel(scatterplot(rand(100)))
) / (
UnicodePlots.panel(lineplot(2:-1:1)) *
UnicodePlots.panel(densityplot(randn(1_000), randn(1_000)))
) |> display
gridplot(map(i -> lineplot(-i:i), 1:5); show_placeholder=true) |> display
gridplot(map(i -> lineplot(-i:i), 1:3); layout=(2, nothing)) |> display
gridplot(map(i -> lineplot(-i:i), 1:3); layout=(nothing, 1)) |> display
...
Using a non true monospace font
can lead to visual problems on a BrailleCanvas
(border versus canvas).
Either change the font to e.g. JuliaMono or use border=:dotted
keyword argument in the plots.
For a Jupyter
notebook with the IJulia
kernel see here.
(Experimental) Terminals seem to respect a standard aspect ratio of 4:3
, hence a square matrix does not often look square in the terminal.
You can pass the experimental keyword fix_ar=true
to spy
or heatmap
in order to recover a unit aspect ratio.
...
Non-exhaustive methods description:
-
title!(plot::Plot, title::String)
title
the string to write in the top center of the plot window. If the title is empty the whole line of the title will not be drawn
-
xlabel!(plot::Plot, xlabel::String)
xlabel
the string to display on the bottom of the plot window. If the title is empty the whole line of the label will not be drawn
-
ylabel!(plot::Plot, xlabel::String)
ylabel
the string to display on the far left of the plot window.
The method label!
is responsible for the setting all the textual decorations of a plot. It has two functions:
-
label!(plot::Plot, where::Symbol, value::String)
where
can be any of::tl
(top-left),:t
(top-center),:tr
(top-right),:bl
(bottom-left),:b
(bottom-center),:br
(bottom-right),:l
(left),:r
(right)
-
label!(plot::Plot, where::Symbol, row::Int, value::String)
-
where
can be any of::l
(left),:r
(right) -
row
can be between 1 and the number of character rows of the canvas
-
x = y = collect(1:10)
plt = lineplot(x, y, canvas=DotCanvas, height=10, width=30)
lineplot!(plt, x, reverse(y))
title!(plt, "Plot Title")
for loc in (:tl, :t, :tr, :bl, :b, :br)
label!(plt, loc, string(':', loc))
end
label!(plt, :l, ":l")
label!(plt, :r, ":r")
for i in 1:10
label!(plt, :l, i, string(i))
label!(plt, :r, i, string(i))
end
plt
annotate!(plot::Plot, x::Number, y::Number, text::AbstractString; kw...)
text
arbitrary annotation at position (x, y)
...
All plots support the set (or a subset) of the following named parameters:
-
symbols::Array = ['■']
: collection of characters used to render the bars. -
title::String = ""
: text displayed on top of the plot. -
name::String = ""
: current drawing annotation displayed on the right. -
xlabel::String = ""
: text displayed on thex
axis of the plot. -
ylabel::String = ""
: text displayed on they
axis of the plot. -
zlabel::String = ""
: text displayed on thez
axis (colorbar) of the plot. -
xscale::Symbol = :identity
:x
-axis scale (:identity
,:ln
,:log2
,:log10
), or scale function e.g.x -> log10(x)
. -
yscale::Symbol = :identity
:y
-axis scale. -
labels::Bool = true
: show plot labels.lineplot(1:.5:20, sin, labels=false)
-
border::Symbol = :solid
: plot bounding box style (:corners
,:solid
,:bold
,:dashed
,:dotted
,:ascii
,:none
).lineplot([-1., 2, 3, 7], [1.,2, 9, 4], canvas=DotCanvas, border=:dashed)
lineplot([-1., 2, 3, 7], [1.,2, 9, 4], canvas=DotCanvas, border=:ascii)
lineplot([-1., 2, 3, 7], [1.,2, 9, 4], canvas=DotCanvas, border=:bold)
lineplot([-1., 2, 3, 7], [1.,2, 9, 4], border=:dotted)
lineplot([-1., 2, 3, 7], [1.,2, 9, 4], border=:none)
-
margin::Int = 3
: number of empty characters to the left of the whole plot. -
padding::Int = 1
: left and right space between the labels and the canvas. -
color::Symbol = :auto
: choose from (:green
,:blue
,:red
,:yellow
,:cyan
,:magenta
,:white
,:normal
,:auto
), use an integer in[0-255]
, or provide3
integers asRGB
components. -
height::Int = 15
: number of canvas rows, or:auto
.lineplot(1:.5:20, sin, height=18)
-
width::Int = 40
: number of characters per canvas row, or:auto
.lineplot(1:.5:20, sin, width=60)
-
xlim::Tuple = (0, 0)
: plotting range for thex
axis ((0, 0)
stands for automatic). -
ylim::Tuple = (0, 0)
: plotting range for they
axis. -
zlim::Tuple = (0, 0)
: colormap scaled data range. -
xticks::Bool = true
: setfalse
to disable ticks (labels) onx
-axis. -
yticks::Bool = true
: setfalse
to disable ticks (labels) ony
-axis. -
xflip::Bool = false
: settrue
to flip thex
axis. -
yflip::Bool = false
: settrue
to flip they
axis. -
colorbar::Bool = false
: toggle the colorbar. -
colormap::Symbol = :viridis
: choose a symbol fromColorSchemes.jl
e.g.:viridis
, or supply a functionf: (z, zmin, zmax) -> Int(0-255)
, or a vector of RGB tuples. -
colorbar_lim::Tuple = (0, 1)
: colorbar limit. -
colorbar_border::Symbol = :solid
: color bar bounding box style (:solid
,:bold
,:dashed
,:dotted
,:ascii
,:none
). -
canvas::UnionAll = BrailleCanvas
: type of canvas used for drawing. -
grid::Bool = true
: draws grid-lines at the origin. -
compact::Bool = false
: compact plot labels. -
unicode_exponent::Bool = true
: useUnicode
symbols for exponents: e.g.10²⸱¹
instead of10^2.1
. -
thousands_separator::Char = ' '
: thousands separator character (useChar(0)
to disable grouping digits). -
projection::Symbol = :orthographic
: projection for 3D plots (:ortho(graphic)
,:persp(ective)
, orModel-View-Projection
(MVP) matrix). -
axes3d::Bool = true
: draw 3d axes (x -> :red
,y -> :green
,z -> :blue
). -
elevation::Float = 35.264389682754654
: elevation angle above or below thefloor
plane (-90 ≤ θ ≤ 90
). -
azimuth::Float = 45.0
: azimutal angle around theup
vector (-180° ≤ φ ≤ 180°
). -
zoom::Float = 1.0
: zooming factor in 3D. -
up::Symbol = :z
: up vector (:x
,:y
or:z
), prefix withm -> -
orp -> +
to change the sign e.g.:mz
for-z
axis pointing upwards. -
near::Float = 1.0
: distance to the near clipping plane (:perspective
projection only). -
far::Float = 100.0
: distance to the far clipping plane (:perspective
projection only). -
canvas_kw::NamedTuple = NamedTuple()
: extra canvas keywords. -
blend::Bool = true
: blend colors on the underlying canvas. -
fix_ar::Bool = false
: fix terminal aspect ratio (experimental). -
visible::Bool = true
: visible canvas.
Note: If you want to print the plot into a file but have monospace issues with your font, you should probably try setting border=:ascii
and canvas=AsciiCanvas
(or canvas=DotCanvas
for scatterplots).
...
The primary structures that do all the heavy lifting behind the curtain are subtypes of Canvas
. A canvas is a graphics object for rasterized plotting. Basically, it uses Unicode characters to represent pixel.
Here is a simple example:
import UnicodePlots: lines!, points!, pixel!
canvas = BrailleCanvas(15, 40, # number of rows and columns (characters)
origin_y=0., origin_x=0., # position in virtual space
height=1., width=1.) # size of the virtual space
lines!(canvas, 0., 0., 1., 1.; color=:cyan) # virtual space
points!(canvas, rand(50), rand(50); color=:red) # virtual space
lines!(canvas, 0., 1., .5, 0.; color=:yellow) # virtual space
pixel!(canvas, 5, 8; color=:red) # pixel space
Plot(canvas)
You can access the height and width of the canvas (in characters) with nrows(canvas)
and ncols(canvas)
respectively. You can use those functions in combination with print_row
to embed the canvas anywhere you wish. For example, print_row(STDOUT, canvas, 3)
writes the third character row of the canvas to the standard output.
As you can see, one issue that arises when multiple pixel are represented by one character is that it is hard to assign color. That is because each of the "pixel" of a character could belong to a different color group (each character can only have a single color). This package deals with this using a color-blend for the whole group. You can disable canvas color blending / mixing by passing blend=false
to any function.
import UnicodePlots: lines!
canvas = BrailleCanvas(15, 40; origin_y=0., origin_x=0., height=1., width=1.)
lines!(canvas, 0., 0., 1., 1.; color=:cyan)
lines!(canvas, .25, 1., .5, 0.; color=:yellow)
lines!(canvas, .2, .8, 1., 0.; color=:red)
Plot(canvas)
The following types of Canvas
are implemented:
-
BrailleCanvas: This type of canvas is probably the one with the highest resolution for
Unicode
plotting. It essentially uses the Unicode characters of the Braille symbols as pixels. This effectively turns every character into eight pixels that can individually be manipulated using binary operations. -
BlockCanvas: This canvas is also
Unicode
based. It has half the resolution of the BrailleCanvas. In contrast toBrailleCanvas
, the pixels don't have visible spacing between them. This canvas effectively turns every character into four pixels that can individually be manipulated using binary operations. -
HeatmapCanvas: This canvas is also
Unicode
based. It has half the resolution of theBlockCanvas
. This canvas effectively turns every character into two color pixels, using the foreground and background terminal colors. As such, the number of rows of the canvas is half the number ofy
coordinates being displayed. -
AsciiCanvas and DotCanvas: These two canvas utilizes only standard
ASCII
character for drawing. Naturally, it doesn't look quite as nice as the Unicode-based ones. However, in some situations it might yield better results. Printing plots to a file is one of those situations. -
DensityCanvas: Unlike the
BrailleCanvas
, the density canvas does not simply mark a "pixel" as set. Instead it increments a counter per character that keeps track of the frequency of pixels drawn in that character. Together with a variable that keeps track of the maximum frequency, the canvas can thus draw the density of data-points. -
BarplotGraphics: This graphics area is special in that it does not support any pixel manipulation. It is essentially the barplot without decorations but the numbers. It does only support one method
addrow!
which allows the user to add additional bars to the graphics object.
...
Because Julia uses column-major indexing order for an array type, and because displaying data on a terminal is row based, we need an internal buffer compatible with efficient columns based iteration. We solve this by using the transpose of a (width
, height
) array for indexing into an internal buffer like buf[row, col]
or buf[y, x]
.
Common users of UnicodePlots don't need to be aware of this axis difference if sticking to public interface.
p = Plot([NaN], [NaN]; xlim=(1, 10), ylim=(1, 10), title="internal buffer conventions")
# plot axes
vline!(p, 1, head_tail=:head, color=:green, name="y-axis (rows)")
hline!(p, 1, head_tail=:head, color=:red, name="x-axis (cols)")
# square
vline!(p, 2, [2, 9], color=:cyan, name="buf[y, x] - buf[row, col]")
vline!(p, [2, 9], [2, 9], color=:cyan)
hline!(p, [2, 9], [2, 9], color=:cyan)
# internal axes
vline!(p, 3, range(3, 8; length=20), head_tail=:tail, color=:light_green, name="y-buffer (rows)")
hline!(p, 8, range(3, 8; length=20), head_tail=:head, color=:light_red, name="x-buffer (cols)")
# mem layout
vline!(p, 4, [4, 7]; color=:yellow, name="memory layout")
vline!(p, 7, [4, 7]; color=:yellow)
hline!(p, [4, 7], [4, 7]; color=:yellow)
hline!(p, [4.5, 5, 5.5, 6], [4.5, 6.5]; color=:yellow)
...
Run the following snippet to analyze invalidations:
using SnoopCompileCore
invalidations = @snoopr using UnicodePlots
tinf = @snoopi_deep UnicodePlots.precompile_workload()
using SnoopCompile, AbstractTrees, PrettyTables # must occur after `invalidations`
print_tree(tinf; maxdepth = typemax(Int))
trees = invalidation_trees(invalidations)
trees = filtermod(UnicodePlots, trees; recursive = true)
@show length(uinvalidated(invalidations)) # all invalidations
# only from `UnicodePlots`
@show length(staleinstances(tinf))
@show length(trees)
SnoopCompile.report_invalidations(;
invalidations,
process_filename = x -> last(split(x, ".julia/packages/")),
n_rows = 0,
)
...
The following snippet:
$ cd docs
$ julia gen_docs.jl
$ (cd imgs; julia gen_imgs.jl)
will regenerate README.md
and the example images with root (prefix) url https://github.com/JuliaPlots/UnicodePlots.jl/raw/unicodeplots-docs.
This code is free to use under the terms of the MIT license.
Inspired by TextPlots.jl, which in turn was inspired by Drawille.