# PythonPlot: use imshow instead of pcolormesh for heatmaps

**URL:** https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217
**Category:** General Usage
**Tags:** plotting, pythonplot
**Created:** [August 15, 2024, 9:16am UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217 "2024-08-15T09:16:14Z")
**Posts on this page:** 14
**Page:** 1

<div class="post-metadata">

### Author: ![maltee1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/maltee1/32/204977_2.png) [@maltee1](https://discourse.julialang.org/u/maltee1)
#### Post date: [August 15, 2024, 9:16am UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/1 "2024-08-15T09:16:14Z")

</div>

I have heatmaps to plot with julia. I sometimes use the plotlyjs backend to zoom portions of the plot, but for export I use pythonplot because all other backends I’ve tried either have problems (e.g. can’t do logarithmic scaling of the colorbar, lack of features I need) or just don’t look good.

The problem is that pythonplot’s heatmap seems to use pcolormesh, which turns every datapoint into a colored rectangle. With around 100 000 data points, that’s really slow, rendering the plot unusable. My current workaround is to plot using both pythonplot and gr, then delete the colored rectangles in inkscape and copy over and scale the bitmap from the gr plot. That may take 10-15 minutes because inkscape freezes for a few minutes every time I try to do something.

Is it possible to instead use imshow, but otherwise using the same commands? It should be possible to achieve the same layout and apperance. I really don’t want to use matplotlib directly, its syntax is unwieldy and incompatible with everything else, which I want to optionally use as well.

Cheers,  
Malte

---

<div class="post-metadata">

### Author: ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)
#### Post date: [August 15, 2024, 10:23am UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/2 "2024-08-15T10:23:41Z")

</div>

> [@maltee1](#):
>
> 100 000 data points, that’s really slow,

That is only a 317\*317 heatmap, which is fast.

---

<div class="post-metadata">

### Author: ![maltee1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/maltee1/32/204977_2.png) [@maltee1](https://discourse.julialang.org/u/maltee1)
#### Post date: [August 15, 2024, 11:18am UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/3 "2024-08-15T11:18:10Z")

</div>

> [@rafael.guerra](#):
>
> which is fast

It’s fast with GR and PlotlyJS, but it’s very very slow with PythonPlot or PGFPlotsX because these generate a colored rectangle for each pixel, rather than embedding a bitmap. Maybe the plotting itself isn’t that slow, but displaying the heatmap definitely is and editing or embedding the plot in a document even more so.

Example code:

```julia
using Plots; pythonplot()

@time begin
    A = rand(300,300)
    heatmap(A)
    savefig("heatmap.svg")
end

```

Output

```julia
9.303465 seconds (4.89 M allocations: 101.184 MiB, 0.96% gc time)

```

generates a 16 MB (!) file

now change `pythonplot()` to `gr()` and you get

```julia
0.269523 seconds (726.28 k allocations: 25.478 MiB)

```

generates a 647 kB file.

for more fun, try opening the 16 MB heatmap.svg in inkscape and try editing it.

edit:  
The winner seems to be `pgfplotsx()` with

```julia
222.957521 seconds (3.08 M allocations: 591.857 MiB, 0.04% gc time)

```

and a filesize of a whopping 37 MB. Saving to .tex instead of .svg is much faster at 0.4 seconds, the file will be 6 MB

`plotlyjs()` is nothing out of the ordinary, with 0.6 seconds excecution time and a file size of 477 kB.

---

<div class="post-metadata">

### Author: ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)
#### Post date: [August 15, 2024, 11:48am UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/4 "2024-08-15T11:48:51Z")

</div>

Ok, so the issue is not the display, but saving in \*.svg format.  
If you save in \*.png format it takes 0.2 s.

---

<div class="post-metadata">

### Author: ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)
#### Post date: [August 15, 2024, 11:58am UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/5 "2024-08-15T11:58:26Z")

</div>

Your options seem to be for SVG output these backends with Plots.jl:

[https://docs.juliaplots.org/latest/output/](https://docs.juliaplots.org/latest/output/)

> gr, inspectdr, pgfplotsx, plotlyjs, pythonplot, gaston

You seem to be looking for a workaround for matplotlib, i.e. PythonPlot:  
[https://docs.juliaplots.org/stable/backends/#At-a-glance](https://docs.juliaplots.org/stable/backends/#At-a-glance)

As mentioned you can use PNG not just SVG with it or most backends:

> [@CairoMakie - SVG export options](https://discourse.julialang.org/t/cairomakie-svg-export-options/90348):
>
> Hi all, when saving Makie plots as svg, all text is converted to curves (i am using Arial font in case that matters). I would like to have the text as actual text instead so that it is easier to edit later on using other programs. Is this possible? I couldn’t find an applicable setting in CairoMakie. The only options I found [in the docs](https://docs.makie.org/stable/documentation/backends/cairomakie/) were for partial rasterization or overall scaling of the output. I know that Plots.jl exports text as text as long as the font is not missing [[link]](https://discourse.julialang.org/t/plots-export-to-svg-with-text-converted-to-shape/47420), and in m…

I doubt SVG can be made fast, it’s just inherently scales linearally to be slower, with more points, or even if there’s some workaround, PlythonPlot (and PyPlot) are limited to what matplotlib does (I suppose they support all the features).

Have you thought of using not just a different backend for Plots, rather use Makie? I think it’s going to be the future of Julia plotting, if not already. It has it’s own backends.

> **[heatmap | Makie](https://docs.makie.org/stable/reference/plots/heatmap)**
>
> Create impressive data visualizations with Makie, the plotting ecosystem for the Julia language. Build aesthetic plots with beautiful customizable themes, control every last detail of publication quality vector graphics, assemble complex layouts and...

I understand the tidier ecosystem is very nice (a go-to by now for people over using DataFrames directly, and not just from people coming from R), and it has:

> **[GitHub - TidierOrg/TidierPlots.jl: Tidier data visualization in Julia, modeled after...](https://github.com/TidierOrg/TidierPlots.jl)**
>
> Tidier data visualization in Julia, modeled after the ggplot2 R package.

It uses Makie as its plotting package.

> [@maltee1](#):
>
> My current workaround is to plot using both pythonplot and gr

What do you even mean? Different plots, or same plot with both, and editing into one by hand? That seems unworkable, not a scalable solution, if you need to regenerate the plots at least. I at least thought with Plots.jl you choose just one backend, but I suppose you can switch dynamically if it suits you, it’s just seems like a giant hack to be avoided what you are doing.

---

<div class="post-metadata">

### Author: ![maltee1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/maltee1/32/204977_2.png) [@maltee1](https://discourse.julialang.org/u/maltee1)
#### Post date: [August 15, 2024, 12:35pm UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/6 "2024-08-15T12:35:21Z")

</div>

> [@rafael.guerra](#):
>
> If you save in \*.png format it takes 0.2 s.

Except .png rasterizes everything. Text, ticks, axes etc. should be vector graphics. Just the heatmap should be an embedded bitmap, because it’s essentially pixels - that’s exactly what bitmaps are for.  
GR does that when I save to svg - heatmap as embedded bitmap, everything else as vector graphics. The problem is, GR messes up the plot by overlapping text, it doesn’t seem to be drawing ticks and it can’t do logarithmic colorbars. Which may be fixable, but requires a lot of hacking.

> [@Palli](#):
>
> What do you even mean? Different plots, or same plot with both, and editing into one by hand? That seems unworkable, not a scalable solution, if you need to regenerate the plots at least. I at least thought with [Plots.jl](https://juliahub.com/ui/Packages/General/Plots) you choose just one backend, but I suppose you can switch dynamically if it suits you, it’s just seems like a giant hack to be avoided what you are doing.

Yes, you understood that exactly right. It’s a terrible solution, but it’s the only way I seem to be able to generate acceptable heatmaps.

I remember trying makie when fixing my logarithmic colorbar problem - I think that also didn’t work. PythonPlot is the only one that does that well, it does a really good job with line plots, but it sucks for heatmaps. I don’t want to draw plots with different backends for the same document to work around that.

Makie doesn’t seem to be using bitmaps either:

> **MakieCore.heatmap** — _Function_.  
> Plots a heatmap as a collection of rectangles

---

<div class="post-metadata">

### Author: ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)
#### Post date: [August 15, 2024, 1:17pm UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/7 "2024-08-15T13:17:51Z")

</div>

If you rasterize in pythonplot() with dpi=1200, it takes 2.4 s to generate the png file. Is this resolution good enough?

---

<div class="post-metadata">

### Author: ![maltee1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/maltee1/32/204977_2.png) [@maltee1](https://discourse.julialang.org/u/maltee1)
#### Post date: [August 15, 2024, 1:28pm UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/8 "2024-08-15T13:28:56Z")

</div>

> [@rafael.guerra](#):
>
> png

No, I want an SVG file. Only the heatmap itself should be stored as a bitmap. A high resolution bitmap is a terrible solution. It still looks awful (albeit not as awful as a low resolution bitmap) and it takes up far more storage space than necessary.

If it only was excecution time it wouldn’t be half as bad. The problem with SVG-rectangles instead of proper pixels is storage space and appearance. My document would be tens to maybe hundreds of MBs and take forever to render. A high resolution PNG is somewhere in between - still looks terrible, uses a ton of storage, but probably renders at acceptable speeds.

The thing is, I can get exactly what I want with a few clicks in Inkscape. But the process is manual and takes forever. What I want is default behavior with the majority of commercial tools and even default on the GR backend. Even python tools like imshow can do this, but it’s not integrated in Julia Plots, so it’s tedious to use.

---

<div class="post-metadata">

### Author: ![JonasWickman](https://avatars.discourse-cdn.com/v4/letter/j/9de0a6/32.png) [@JonasWickman](https://discourse.julialang.org/u/JonasWickman)
#### Post date: [August 15, 2024, 1:54pm UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/9 "2024-08-15T13:54:15Z")

</div>

> [@maltee1](#):
>
> Makie doesn’t seem to be using bitmaps either:
> 
> > **MakieCore.heatmap** — _Function_.  
> > Plots a heatmap as a collection of rectangles

This is a little off topic as you wanted to use Plots.jl, not Makie, but you can rasterize the heatmap (while keeping everything else in vector format) with Makie:

```julia
using CairoMakie

fig = Figure()
xs = range(-1, 1; length = 301)
ys = xs'

Zs = cos.(2π.*xs).*sin.(2π.*ys)

ax = Axis(fig[1,1]; xlabel = "x-axis", ylabel = "y-axis")
hm = heatmap!(ax, xs, xs, Zs; rasterize = 5)
Colorbar(fig[1,2], hm)

save("hm.pdf", fig)

```

Filesize: ~234 kB

---

<div class="post-metadata">

### Author: ![maltee1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/maltee1/32/204977_2.png) [@maltee1](https://discourse.julialang.org/u/maltee1)
#### Post date: [August 15, 2024, 2:28pm UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/10 "2024-08-15T14:28:34Z")

</div>

I there really no way to use `pythonplot`? All my other plots are already using it, I’d have to redo everything.

---

<div class="post-metadata">

### Author: ![JonasWickman](https://avatars.discourse-cdn.com/v4/letter/j/9de0a6/32.png) [@JonasWickman](https://discourse.julialang.org/u/JonasWickman)
#### Post date: [August 15, 2024, 2:41pm UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/11 "2024-08-15T14:41:09Z")

</div>

I really don’t know the Plots.jl ecosystem at all and haven’t used PyPlot/Matplotlib in a long time, but matplotlib does support rasterization: [Rasterization for vector graphics — Matplotlib 3.9.2 documentation](https://matplotlib.org/stable/gallery/misc/rasterization_demo.html). Thus, if you can somehow extract the python objects from your Plots.jl plot it should be possible to directly call the Matplotlib functions on them. No clue on how to do this in any more detail, alas.

---

<div class="post-metadata">

### Author: ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)
#### Post date: [August 15, 2024, 5:19pm UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/12 "2024-08-15T17:19:24Z")

</div>

> [@JonasWickman](#):
>
> but matplotlib does support rasterization: [Rasterization for vector graphics — Matplotlib 3.9.2 documentation](https://matplotlib.org/stable/gallery/misc/rasterization_demo.html). Thus, if you can somehow extract the python objects

It’s not obvious to me that `set_rasterized` or `Axes.set_rasterization_zorder` is supported in PythonPlot’s API (I looked for it in the code and in PyPlots and in Plots), i.e. that it has complete API coverage, without accessing directly, but such a hack seems possible. Then neither would Plots have full API, so you’ll need to access the underlying object twice from there.

You might want to first try if you can do what use that, i.e. do what you want with matplotlib directly (with PythonCall or in python itself), and if you can then from PythonCall seemingly though `PythonPlot.matplotlib`:

> ### Exported functions
> 
> Only the currently documented `matplotlib.pyplot` API is exported. To use other functions in the module, you can also call `matplotlib.pyplot.foo(...)` as `pyplot.foo(...)`. For example, `pyplot.plot(x, y)` also works. (And the raw `Py` object for the `matplotlib` module itself is also accessible as `PythonPlot.matplotlib`.)
> 
> […]  
> You must also explicitly qualify some functions built-in Julia functions. In particular, `PythonPlot.xcorr`, `PythonPlot.axes`, and `PythonPlot.isinteractive` must be used to access `matplotlib.pyplot.xcorr` etcetera.
> 
> If you wish to access _all_ of the PyPlot functions exclusively through `pyplot.somefunction(...)`, as is conventional in Python, you can do `import PythonPlot as pyplot` instead of `using PythonPlot`.

[PythonPlot is likely a fork of PyPlot.jl why it says PyPlot, not PythonPlot in the docs.]

Note if you use Plots then seemingly you need to use: [`Plots.backend_object`](https://docs.juliaplots.org/stable/api/#Plots.backend_object)

So possibly like Plots.backend\_object.PythonPlot.matplotlib.set\_rasterized ?

When looking up `zorder` in Plots I only found: [plotting order in figure · Issue #236 · JuliaPy/PyPlot.jl · GitHub](https://github.com/JuliaPy/PyPlot.jl/issues/236)

---

<div class="post-metadata">

### Author: ![JonasWickman](https://avatars.discourse-cdn.com/v4/letter/j/9de0a6/32.png) [@JonasWickman](https://discourse.julialang.org/u/JonasWickman)
#### Post date: [August 15, 2024, 7:24pm UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/13 "2024-08-15T19:24:41Z")

</div>

The following very hacky method appears to work with some caveats. Here’s a simplified example. First run this:

```julia
using Plots
import PythonPlot
pythonplot()
xs = range(-1, 1; length = 301)
ys = xs' 
Zs = @. cos(2π*xs)*sin(2π*ys)
ret_val = heatmap(xs, xs, Zs)

```

Wait for whatever Plots/Python is doing (julia will run the next command too soon otherwise). Then run this:

```julia
quad_mesh = ret_val.series_list[1][:serieshandle][1]
quad_mesh.set_rasterized(true)
ret_val.o.savefig("hm.pdf")

```

The output file is small (~32 kB) with only the heatmap rasterized. For your actual figure, you’ll have to try to hunt down which series is the one you need to extract. If it’s a heatmap, its serieshandle should be a `matplotlib.collections.QuadMesh object`

---

<div class="post-metadata">

### Author: ![maltee1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/maltee1/32/204977_2.png) [@maltee1](https://discourse.julialang.org/u/maltee1)
#### Post date: [August 16, 2024, 8:44am UTC](https://discourse.julialang.org/t/pythonplot-use-imshow-instead-of-pcolormesh-for-heatmaps/118217/14 "2024-08-16T08:44:08Z")

</div>

> [@JonasWickman](#):
>
> Wait for whatever Plots/Python is doing (julia will run the next command too soon otherwise). Then run this:
> 
> ```julia
> quad_mesh = ret_val.series_list[1][:serieshandle][1]
> quad_mesh.set_rasterized(true)
> ret_val.o.savefig("hm.pdf")
> 
> ```
> 
> The output file is small (~32 kB) with only the heatmap rasterized. For your actual figure, you’ll have to try to hunt down which series is the one you need to extract. If it’s a heatmap, its serieshandle should be a `matplotlib.collections.QuadMesh object`

YES!!! this is exactly what I needed. Quick, easy scalable. Thank you so much!  
Now if you also know how to change the zorder of different plot elements, because the heatmap is above the ticks and the grid lines, I would not have to use inkscape anymore.

Nevermind, I found it:  
`quad_mesh.zorder=-1`

Maybe you can help me understand what `:serieshandle` is. I thought “wait for whatever Plots/Python is doing” meant i could just add a sleep(). But it seems that `:serieshandle` is only available if I run the second block separately.
