Skip to content

code

PyPlot.jl tips

Some tips about JuliaPy/PyPlot.jl, the matplotlib (Python) visualization library for Julia. See also docs for matplotlib since PyPlot.jl largely follows matplotlib's API.

How to solve PyPlot.jl installation errors

Since PyPlot.jl depends on the Python package matplotlib, sometimes simply ]add the package will not work due to some quirks in the installation process.

In a local computer, it is recommended to have a clean Conda environment inside Julia to minimize issues. To enforce a local miniconda environment inside Julia, set the PYTHON environment variable to an empty string.

~/.julia/config/startup.jl
ENV["PYTHON"]=""

And then rebuild related packages.

import Pkg
Pkg.build(["PyCall", "Conda", "PyPlot"])

# should download conda and matplotlib and import PyPlot
using PyPlot

How to share the legend box in double y axis

  • Capture line plot objects from both axes.
  • Call legend() for both(all) line plot objects.
import PyPlot as plt
x1 = 1:10

fig, ax1 = plt.subplots()
l1 = ax1.plot(x1, x1)
ax2 = ax1.twinx()
l2= ax2.plot(x1, exp.(x1))
ax1.legend([first(l1), first(l2)], ["x", "exp(x)"])

How to save as TIFF image

Exporting pyplot figures to TIFF images with a higher dpi and LZW compression.

PyPlot.jl

fig.savefig("fig.tif", dpi=300, pil_kwargs=Dict("compression" => "tiff_lzw"))

PythonPlot.jl

using PythonCall
plt.savefig("fig1.tif", dpi=300, pil_kwargs=pydict(Dict("compression" => "tiff_lzw")))

Change default options in PyPlot.jl

Changing mplstyle and rcparams in matplotlib.

Sources:
1. mplstyle and rcparams for matplotlib
2. PyPlot.jl readme
3. mplstyle and rcparams in matplotlib docs.

Python

matplotlib

import matplotlib as mpl
mpl.rcParams["font.sans-serif"] = "Arial"
mpl.rcParams["font.family"] = "sans-serif"
mpl.rcParams["font.size"] = 12

Julia

PyPlot.jl

import PyPlot as plt
rcParams = plt.PyDict(plt.matplotlib."rcParams")
rcParams["font.sans-serif"] = "Arial"
rcParams["font.family"] = "sans-serif"
rcParams["font.size"] = 12

For Plots.jl, there is an internal pyrcparams dictionary for the pyplot(matplotlib) backend.

using Plots
Plots.pyplot()
Plots.pyrcparams["font.size"] = 12
Plots.pyrcparams["font.sans-serif"] = "Arial"
Plots.pyrcparams["font.family"] = "sans-serif"

PythonPlot.jl

import PythonPlot as plt
plt.matplotlib.rcParams["font.size"] = 14

Dealing with DomainErrors

sqrt(x), log(x), and pow(x) will throw DomainError exceptions with negative x, interrupting differential equation solvers. One can use these functions' counterparts in JuliaMath/NaNMath.jl, returning NaN instead of throwing a DomainError. Then, the solvers will reject the solution and retry with a smaller time step.

sqrt(-1.0) # throws DomainError

import NaNMath as nm
nm.sqrt(-1.0) # NaN

Force display format to PNG

In the VSCode plot panel and fredrikekre/Literate.jl notebooks, PNG images are generally smaller than SVG ones. To force plots to be shown as PNG images, you can use tkf/DisplayAs.jl to show objects in a chosen MIME type.

import DisplayAs.PNG
using Plots
plot(rand(6)) |> PNG

If you don't want to add another package dependency, you could directly use display().

using Plots
PNG(img) = display("image/png", img)
plot(rand(6)) |> PNG

Get the ODE Function from an ODE system

f = ODEFunction(sys) could be useful in plotting vector fields.

using ModelingToolkit
using DifferentialEquations
using Plots

# Independent (time) and dependent (state) variables (x and RHS)
@variables t x(t) RHS(t)

# Setting parameters in the modeling
@parameters τ

# Differential operator w.r.t. time
D = Differential(t)

# Equations in MTK use the tilde character (`~`) as equality.
# Every MTK system requires a name. The `@named` macro simply ensures that the symbolic name matches the name in the REPL.

@named fol_separate = ODESystem([
    RHS  ~ (1 - x)/τ,
    D(x) ~ RHS
])

sys = structural_simplify(fol_separate)

f = ODEFunction(sys)

f([0.0], [1.0], 0.0) # f(u, p, t) returns the value of D(x)

Get 2D indices from a linear index

Use CartesianIndices((nrow, ncol)), from this discourse post.

x = rand((7, 10))
CI = CartesianIndices((7, 10))
for i in 1:length(x)
    r = CI[i][1]
    c = CI[i][2]
    @assert x[i] == x[r, c]
end

Plots.jl Tips

Some tips about Plots.jl, the de-facto standard visualization library in Julia.

You don't have to precalculate

Plots.jl supports tracing functions.

  • plot(f, tmin, tmax) or plot(f, tArray)
  • plot(fx, fy, tmin, tmax) or plot(fx, fy, tArray)

For example, you can easily draw a parametric plot for x(t) and y(t).

# plotting (x(t), y(t))
plot(sin, (x-> sin(2x)), 0, 2π, line = 4, leg = false)

Or make a contour plot without precalculating the function values

x = 1:0.5:20
y = 1:0.5:10

g(x, y) = (3x + y ^ 2) * abs(sin(x) + cos(y))

# Precalculate the value
X = repeat(reshape(x, 1, :), length(y), 1)
Y = repeat(y, 1, length(x))
Z = map(g, X, Y)

p2 = contour(x, y, Z)

# Generate z value on-the-fly

p1 = contour(x, y, g, fill=true)
plot(p1, p2)

Subplots

You can combine multiple plots into one single plot with a @layout.

l = @layout [a ; b c]
p1 = plot(...)
p2 = plot(...)
p3 = plot(...)

plot(p1, p2, p3, layout = l)

Or use the sp keyword argument

plot(layout=(2,2))

plot!(randn(50), sp=1)
plot!(randn(50), sp=2)
plot!(randn(50), sp=3)
plot!(randn(50), sp=4)

See layouts for more options.

List supported styles

# tip: use Plots.supported_styles() or Plots.supported_markers() to see which line styles or marker shapes you can use
@show Plots.supported_styles();
@show Plots.supported_markers();

See plot attributes.

Render Images

# using Pkg; Pkg.add("Images")

using Images
img1 = load("dog.jpg")
plot(img1)

LaTeX texts

Plots.jl supports LaTeX texts in the figure.

# using Pkg; Pkg.add("LaTeXStrings")
using LaTeXStrings
str = L"\textrm{Count}"

PDF output

  • PDF uses vector graphic format and conserves details.
  • Set figure size e.g. size=(750,750) to determine the relative fonts sizes.
  • One can convert PDF to tiff images later using pdftoppm or imagemagick.

Shared Color bar

Source. The trick is to
- make an blank scatter plot for the colorbar.
- use a dedicated space in the layout for the colorbar.

using Plots

x1 = range(0.0, 1.0, length=51)
y1 = range(-1.0, 0.0, length=51)
z1 = sin.(4*pi.*x1)*cos.(6*pi.*reshape(y1, 1, :))

x2 = range(0.25, 1.25, length=51)
y2 = range(-1.0, 0.0, length=51)
z2 = 2.0.*sin.(4*pi.*x2)*cos.(6*pi.*reshape(y2, 1, :))

x3 = range(0.0, 1.0, length=51)
y3 = range(-1.25, -0.25, length=51)
z3 = 3.0*sin.(4*pi.*x3)*cos.(6*pi.*reshape(y3, 1, :))

x4 = range(0.25, 1.25, length=51)
y4 = range(-1.25, -0.25, length=51)
z4 = 4.0.*sin.(4*pi.*x4)*cos.(6*pi.*reshape(y4, 1, :))

clims = extrema([z1; z2; z3; z4])

p1 = contourf(x1, y1, z1, clims=clims, c=:viridis, colorbar=false)
p2 = contourf(x2, y2, z2, clims=clims, c=:viridis, colorbar=false)
p3 = contourf(x3, y3, z3, clims=clims, c=:viridis, colorbar=false)
p4 = contourf(x4, y4, z4, clims=clims, c=:viridis, colorbar=false)

h2 = scatter([0,0], [0,1], zcolor=[0,3], clims=clims,
                xlims=(1,1.1), label="", c=:viridis,
                colorbar_title="cbar", framestyle=:none)

l = @layout [grid(2, 2) a{0.035w}]

p_all = plot(p1, p2, p3, p4, h2, layout=l, link=:all)