matplotlib / mplcairo

A (new) cairo backend for Matplotlib.
MIT License
106 stars 22 forks source link

:question: No SVG output when using Cairo operator (SCREEN)? #32

Closed soykje closed 2 years ago

soykje commented 2 years ago

Hello :wave:

I'm getting started with Matplotlib, and working on some dataviz personal project. For this purpose I wanted to use specific operators, such as SCREEN, on drawn polygons.

Result is fine, but I'm having an issue (?) when I want to save (using plt.savefig()) my figure to SVG format: without operator SVG output is just fine, but with SCREEN operator (or others...) I get a wrapped PNG (inside a <image />). Is this expected?

As I said, I'm getting started with Matplotlib, custom backend and also Python (!), so maybe there is something I'm missing here, or doing wrong? Anyway, any help would be really great!

Thx in advance :pray:

Sample code (from actual local code):

import matplotlib
from matplotlib import cm
from matplotlib import colors
from matplotlib import pyplot as plt

matplotlib.use("module://mplcairo.qt")
from mplcairo import operator_t

import numpy as np

def draw(axe, frame, frameset, cmap, operator=None):
  theta = np.linspace(0, 2 * np.pi, len(frame), endpoint=False)
  norm = colors.Normalize(vmin=np.min(frameset), vmax=np.max(frameset))

  d = np.append(frame, frame[:1])
  a = np.append(theta, theta[:1])

  r = np.power(d, 1/2) # re-scaled radii
  c = cmap(norm(np.mean(d))) # re-scaled colors

  polygons = axe.fill(a, r, facecolor=c, linewidth=0.75, alpha=0.5)

  # Optional operator
  if operator:
    for p in polygons:
      # See https://cairographics.org/operators/
      operator_t[operator].patch_artist(p)

# Let's plot!
fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(projection='polar')
# Reset angle starting point and direction
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
# Remove grid and ticks
ax.set_axis_off()
# The figure patch should be fully transparent to avoid
# compositing the patches against it
fig.patch.set(alpha=0)

frames = [[0, 2, 4], [1, 3, 5], [2, 4, 6]] # np.ndarray

for f in frames:
  draw(ax, f, frames, cm.get_cmap('Reds'), 'SCREEN')

plt.tight_layout()
plt.savefig('test.svg', format='svg')

Result of mplcairo.get_versions():

{
  'python': '3.8.5 (default, Sep  4 2020, 07:30:14) \n[GCC 7.3.0]',
  'mplcairo': '0.4',
  'matplotlib': '3.4.2',
  'cairo': '1.16.0',
  'freetype': '2.10.4',
  'pybind11': '2.6.2',
  'raqm': None,
  'hb': None
}
anntzer commented 2 years ago

I haven't actually checked, but my guess is that the svg format simply does not support custom operators like SCREEN (or cairo is not aware of such support), so cairo can only "fake" it by precomputing the rendered overlay and saving it as a raster.

anntzer commented 2 years ago

Actually, I was wrong: svg 1.2 does support these compositing operators, it's just a matter of setting the correct version on the svg... I'll fix that.

soykje commented 2 years ago

Thx a lot for your answers and your time @anntzer! If I understood correctly, this SVG version update would have to be done on lib side, and not through configuration?

anntzer commented 2 years ago

Yes. I have now pushed a commit that should let you do savefig(..., metadata={"MaxVersion": "1.2"}) which should fix the problem for you. (You'll need to build from source yourself; I don't really have the time to release a new version right now, in particular due to unrelated problems with the Windows build.)

Thanks for pointing out the limitation!

soykje commented 2 years ago

Fantastic! I'll test this asap, thx again for your time :+1:

anntzer commented 2 years ago

Closing as this is fixed as of master (unless that still doesn't work for you).