- Notifications
You must be signed in to change notification settings - Fork23
A (new) cairo backend for Matplotlib.
License
matplotlib/mplcairo
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
This is a new, essentially complete implementation of acairo backend forMatplotlib. It can be used in combination with a Qt, GTK, Tk, wx, or macOSUI, or non-interactively (i.e., to save figure to various file formats).
Noteworthy points include:
- Improved accuracy (e.g., with marker positioning, quad meshes, and textkerning; floating point surfaces are supported with cairo≥1.17.2).
- Optional multithreaded drawing of markers and path collections.
- Optional support for complex text layout (right-to-left languages, etc.) andOpenType font features (seeexamples/opentype_features.py) and variations(seeexamples/opentype_variations.py) (requires cairo≥1.16.0), and partialsupport for color fonts (e.g., emojis), usingRaqm.Note that Raqmdepends by default on Fribidi, which is licensed under the LGPLv2.1+.
- Support for embedding URLs in PDF (but not SVG) output (requirescairo≥1.15.4).
- Support for multi-page output both for PDF and PS (Matplotlib only supportsmulti-page PDF).
- Support for custom blend modes (seeexamples/operators.py).
- Improved font embedding in vector formats: fonts are typically subsetted andembedded in their native format (Matplotlib≥3.5 also provides improved fontembedding).
mplcairo requires
- Python≥3.8,
- Matplotlib≥2.2,
- on Linux and macOS, pycairo≥1.16.0[1],
- on Windows, cairo≥1.13.1[2] (shipped with the wheel).
It is recommended to use cairo≥1.17.4.
Additionally, building mplcairo from source requires
- pybind11≥3.0[3],
- pycairo≥1.16.0.
As usual, install using pip:
$ pip install mplcairo# from PyPI$ pip install git+https://github.com/matplotlib/mplcairo# from Github
Note that wheels are not available for macOS<10.13, because the libc++ includedwith these versions is too old and vendoring of libc++ appears to be fragile.
mplcairo can useRaqm (≥0.7.0; ≥0.7.2 is recommended as it provides betteremoji support, especially in the presence of ligatures) for complex text layoutand handling of OpenType font features. Refer to the instructions on thatproject's website for installation on Linux and macOS. On Windows, considerusing Christoph Gohlke'sbuild (the directory containinglibraqm.dll
andlibfribidi-0.dll
need to be added to theDLL searchpath).
[1] | pycairo 1.16.0 added We do not actually rely on pycairo's Python bindings. Rather, thedependency on pycairo (≥1.16.0) conveniently specifies a dependency oncairo (≥1.13.1) itself, and allows us to load cairo at runtime instead oflinking to it (simplifying the build of self-contained wheels). On Windows, this strategy is (AFAIK) not possible, so we explicitly linkagainst the cairo DLL. |
[2] | cairo 1.13.1 matches the oldest version supported by pycairo 1.16.0. cairo 1.15.4 added support for PDF metadata and links; the presence of thisfeature is detected at runtime. cairo 1.16.0 added support for font variations; the presence of this featureis detected at runtime. cairo 1.17.2 added support for floating point surfaces, usable with cairo 1.17.4 fixed a rare crash in rasterization (in dfe3aa6). cairo 1.17.8 fixed a crash when outputting in the cairo-script format (in6a81bf8). |
[3] | pybind11 3.0 supports native enums. |
On Fedora, the package is available aspython-mplcairo.
This section is only relevant if you wish to build mplcairo yourself, orpackage it for redistribution. Otherwise, proceed to theUse section.
In all cases, once the dependencies described below are installed, mplcairocan be built and installed using any of the standard commands (pip wheel--no-deps .
,pip install .
,pip install -e .
andpython setup.pybuild_ext -i
being the most relevant ones).
The following additional dependencies are required:
a C++ compiler with C++17 support, e.g. GCC≥7.2 or Clang≥5.0.
cairo and FreeType headers, and pkg-config information to locate them.
If using conda, they can be installed using
conda install -y -c conda-forge pycairo pkg-config
as pycairo (also a dependency) depends on cairo, which depends on freetype.Note that cairo and pkg-config from the
anaconda
channel willnot work.On Linux, they can also be installed with your distribution's package manager(Arch:
cairo
, Debian/Ubuntu:libcairo2-dev
, Fedora:cairo-devel
).
Raqm (≥0.2) headers are also needed, but will be automatically downloaded ifnot found.
conda's compilers (gxx_linux-64
on theanaconda
channel)currentlyinteract poorly with installing cairo and pkg-config from conda-forge, so you are on your own to install a recent compiler(e.g., using your distribution's package manager). You may want to set theCC
andCXX
environment variables to point to your C++ compiler if it isnonstandard[4]. In that case, be careful to set them to e.g.g++-7
andnotgcc-7
, otherwise the compilation will succeed but the shared objectwill be mis-linked and fail to load.
The manylinux wheel is built usingtools/build-manylinux-wheel.sh.
[4] | distutils usesCC forcompiling C++ sources butCXX forlinking them (don't ask). You may run into additional issues ifCC orCXX has multiple words; e.g., ifCC is set toccache g++ , youalso need to setCXX toccache gcc . |
Clang≥5.0 can be installed fromconda
'sanaconda
channel (condainstall -c anaconda clangxx_osx-64
), or can also be installed with Homebrew(brew install llvm
). Note that Homebrew's llvm formula is keg-only, i.e.it requires manual modifications to the PATH and LDFLAGS (as documented bybrew info llvm
).
On macOS<10.14, it is additionally necessary to use clang<8.0 (e.g. withbrewinstall llvm@7
) as clang 8.0 appears to believe that code relying on C++17can only be run on macOS≥10.14+.
The macOS wheel is built usingtools/build-macos-wheel.sh
, which relies ondelocate-wheel (to vendor a recent version of libc++). Currently, it can onlybe built from a Homebrew-clang wheel, not a conda-clang wheel (due to some pathintricacies...).
The following additional dependencies are required:
VS2019 (The exact minimum version is unknown, but it is known that mplcairofails to build on the Github Actions
windows-2016
agent and requires thewindows-2019
agent.)cairo headers and import and dynamic libraries (
cairo.lib
andcairo.dll
)with FreeType support. Note that this excludes, inparticular, most Anaconda and conda-forge builds: they do not includeFreeType support.The currently preferred solution is to get the headers e.g. from a Linuxdistribution package, the DLL from a pycairo wheel (e.g. from PyPI), andgenerate the import library oneself using
dumpbin
andlib
.Alternatively, very recent conda-forge builds (≥1.16.0 build 1005) doinclude FreeType support. In order to use them, the include path needs to bemodified as described below. (This is currently intentionally disabled bydefault to avoid confusing errors if the cairo build is too old.)
FreeType headers and import and dynamic libraries (
freetype.lib
andfreetype.dll
), which can be retrieved fromhttps://github.com/ubawurinna/freetype-windows-binaries, or alternativelyusing conda:conda install -y freetype
The (standard)CL
andLINK
environment variables (which always getprepended respectively to the invocations of the compiler and the linker)should be set as follows:
set CL=/IC:\path\to\dir\containing\cairo.h /IC:\same\for\ft2build.hset LINK=/LIBPATH:C:\path\to\dir\containing\cairo.lib /LIBPATH:C:\same\for\freetype.lib
In particular, in order to use a conda-forge cairo (as described above),{sys.prefix}\Library\include\cairo
needs to be added to the include path.
Moreover, we also need to findcairo.dll
andfreetype.dll
and copythem next tomplcairo
's extension module. As the dynamic libraries aretypically found next to import libraries, we search the/LIBPATH:
entriesin theLINK
environment variable and copy the firstcairo.dll
andfreetype.dll
found there.
The scripttools/build-windows-wheel.py
automates the retrieval of thecairo (assuming that pycairo is already installed) and FreeType DLLs, and thewheel build.
On Linux and Windows, mplcairo can be used as any normal Matplotlib backend:call e.g.matplotlib.use("module://mplcairo.qt")
before importing pyplot,add abackend: module://mplcairo.qt
line in yourmatplotlibrc
, or settheMPLBACKEND
environment variable tomodule://mplcairo.qt
. Morespecifically, the following backends are provided:
module://mplcairo.base
(No GUI, but can output to EPS, PDF, PS, SVG, andSVGZ using cairo's implementation, rather than Matplotlib's),module://mplcairo.gtk
(GTK widget, copying data from a cairo imagesurface — GTK3 or GTK4 can be selected by callinggi.require_version("Gtk", "3.0")
orgi.require_version("Gtk", "4.0")
before importing the backend),module://mplcairo.gtk_native
(GTK widget, directly drawn onto as anative surface; does not and cannot support blitting — see above for versionselection),module://mplcairo.qt
(Qt widget, copying data from a cairo imagesurface — select the binding to use by importing it before mplcairo, or bysetting theQT_API
environment variable),module://mplcairo.tk
(Tk widget, copying data from a cairo imagesurface),module://mplcairo.wx
(wx widget, copying data from a cairo imagesurface),module://mplcairo.macosx
(macOS widget, copying data from a cairo imagesurface).
On macOS, prior to Matplotlib 3.8,it was necessary to explicitly importmplcairo before importing Matplotlib (unless your Matplotlib is built withsystem_freetype = True
). A practical option was to import mplcairo, thencall e.g.matplotlib.use("module://mplcairo.macosx")
.
Jupyter is entirely unsupported (patches would be appreciated). Onepossibility is to set theMPLCAIRO_PATCH_AGG
environment variable to anon-empty valuebefore importing Matplotlib; this fully replaces the Aggrenderer by the cairo renderer throughout Matplotlib. However, this approachis inefficient (due to the need of copies and conversions between premultipliedARGB32 and straight RGBA8888 buffers); additionally, it does not work withthe wx and macosx backends due to peculiarities of the corresponding canvasclasses. On the other hand, this is currently the only way in which thewebagg-based backends (e.g., Jupyter's interactive widgets) can use mplcairo.
At import-time, mplcairo will attempt to loadRaqm. The use of that librarycan be controlled and checked using theset_options
andget_options
functions.
Theexamples directory contains a few cases where the output of this rendereris arguably more accurate than the one of the default renderer, Agg:
- circle_markers.py andsquare_markers.py: more accurate and faster markerstamping.
- marker_stamping.py: more accurate marker stamping.
- quadmesh.py: better antialiasing of quad meshes, fewer artefacts withmasked data.
- text_kerning.py: improved text kerning.
Install (in the virtualenv)pytest>=3.1.0
andpytest-benchmark
, thencall (e.g.):
pytest --benchmark-group-by=fullfunc --benchmark-timer=time.process_time
Keep in mind that conda-forge's cairo is (on my setup) ~2× slower than a"native" build of cairo.
Runrun-mpl-test-suite.py
(which depends onpytest>=3.2.2
) to run theMatplotlib test suite with the Agg backend patched by the mplcairo backend.Note that Matplotlib must be installed with its test data, which is not thecase when it is installed from conda or from most Linux distributions; instead,it should be installed from PyPI or from source.
Nearly all image comparison tests "fail" as the renderers are fundamentallydifferent; currently, the intent is to manually check the diff images. Passing--tolerance=inf
marks these tests as "passed" (while still textuallyreporting the image differences) so that one can spot issues not related torendering differences. In practice,--tolerance=50
appears to be enough.
Some other (non-image-comparison) tests are also known to fail (they are listedinISSUES.rst
, with the relevant explanations), and automatically skipped.
Runrun-examples.py
to run some examples that exercise some more aspects ofmplcairo.
The artist antialiasing property can be set to any of thecairo_antialias_t
enum values, orTrue
(the default) orFalse
(which is synonym toNONE
).
Setting antialiasing toTrue
usesFAST
antialiasing for lines thickerthan 1/3px andBEST
for lines thinner than that: for lines thinnerthan 1/3px, the former leads to artefacts such as lines disappearing incertain sections (see e.g.test_cycles.test_property_collision_plot
afterforcing the antialiasing toFAST
). The threshold of 1/3px was determinedempirically, seeexamples/thin_line_antialiasing.py.
Note that in order to set thelines.antialiased
orpatch.antialiased
rcparams to acairo_antialias_t
enum value, it is necessary to bypassrcparam validation, using, e.g.
dict.__setitem__(plt.rcParams,"lines.antialiased",antialias_t.FAST)
Thetext.antialiased
rcparam can likewise be set to anycairo_antialias_t
enum value, orTrue
(the default, which maps toSUBPIXEL
—GRAY
is not sufficient to benefit fromRaqm's subpixelpositioning; see alsocairo issue #152) orFalse
(whichmaps toNONE
).
Note that in rare cases, on cairo<1.17.4,FAST
antialiasing can trigger a"double free or corruption" bug in cairo (#44). If you hit thisproblem, consider usingBEST
orNONE
antialiasing (depending on yourquality and speed requirements).
For fast drawing of path with many segments, theagg.path.chunksize
rcparamshould be set to e.g. 1000 (seeexamples/time_drawing_per_element.py for thedetermination of this value); this causes longer paths to be split intoindividually rendered sections of 1000 segments each (directly rendering longerpaths appears to have slightly superlinear complexity).
Thepath.simplify_threshold
rcparam is used to control the accuracy ofmarker stamping, down to an arbitrarily chosen threshold of 1/16px. If thethreshold is set to a lower value, the exact (slower) marker drawing path willbe used. Marker stamping is also implemented for scatter plots (which can havemultiple colors). Likewise, markers of different sizes get mapped into markersof discretized sizes, with an error bounded by the threshold.
NOTE:pcolor
and mplot3d'splot_surface
display some artefactswhere the facets join each other. This is because these functions internallyuse aPathCollection
; this triggers the approximate stamping, andeven without it (by settingpath.simplify_threshold
to zero), cairo'srasterization of the edge between the facets is poor.pcolormesh
(whichinternally uses aQuadMesh
) should generally be preferred overpcolor
anyways.plot_surface
could likewise instead represent the surface usingQuadMesh
, which is drawn without such artefacts.
In order to use a specific font that Matplotlib may be unable to use, pass afilename directly:
frommatplotlib.font_managerimportFontPropertiesfig.text(.5,.5,"hello, world",fontproperties=FontProperties(fname="/path/to/font.ttf"))
or more simply, with Matplotlib≥3.3:
frompathlibimportPathfig.text(.5,.5,"hello, world",font=Path("/path/to/font.ttf"))
mplcairo still relies on Matplotlib's font cache, so fonts unsupported byMatplotlib remain unavailable by other means.
For TTC fonts (and, more generally, font formats that include multiple fontfaces in a single file), thenth font (n≥0) can be selected by appending#n
to the filename (e.g.,"/path/to/font.ttc#1"
).
OpenType font features can be selected by appending|feature,...
to the filename, followed by aHarfBuzz feature string (e.g.,"/path/to/font.otf|frac,onum"
); seeexamples/opentype_features.py. Alanguage tag can likewise be set with|language=...
; currently, thisalways applies to the whole buffer, but a PR adding support for slicing syntax(similar to font features) would be considered.
OpenType font variations can be selected by appending an additional|
tothe filename, followed by aCairo font variation string (e.g.,"/path/to/font.otf||wdth=75"
); seeexamples/opentype_variations.py. Thissupport requires Cairo>=1.16. Note that features are parsed first, so if you donot wish to specify any features, you must specify an empty set with two pipes,i.e.,font.otf|variations
will treatvariations
as features,notvariations.
The syntaxes for selecting TTC subfonts and OpenType font features and languagetags areexperimental and may change, especially if such features areimplemented in Matplotlib itself.
Color fonts (e.g. emojis) are handled.
Matplotlib'sPdfPages
class is deeply tied with the builtinbackend_pdf
(in fact, it cannot even be used with Matplotlib's own cairo backend).Instead, usemplcairo.multipage.MultiPage
for multi-page PDF and PS output.The API is similar:
frommplcairo.multipageimportMultiPagefig1= ...fig2= ...withMultiPage(path_or_stream,metadata=...)asmp:mp.savefig(fig1)mp.savefig(fig2)
See the class' docstring for additional information.
cairo is able to write PDF 1.4 and 1.5 (defaulting to 1.5), PostScript levels 2and 3 (defaulting to 3), and SVG versions 1.1 and 1.2 (defaulting to 1.1).This can be controlled by passing ametadata dict tosavefig
with aMaxVersion
entry, which must be one of the strings"1.4"
/"1.5"
(forpdf),"2"
/"3"
(for ps), or"1.1"
/"1.2"
(for svg).
Setting theMPLCAIRO_SCRIPT_SURFACE
environment variablebefore mplcairois imported tovector
orraster
allows one to save figures (withsavefig
) in the.cairoscript
format, which is a "native script thatmatches the cairo drawing model". The value of the variable determines therendering path used (e.g., whether marker stamping is used at all). This maybe helpful for troubleshooting purposes.
Note that cairo-script output is generally broken on cairo≤1.17.8.
draw_markers
draws a marker at each control point of the given path, whichis the documented behavior, even though all builtin renderers only draw markersat straight or Bézier segment ends.
Due to missing support from cairo:
- SVG output does not support global metadata or set URLs or ids on anyelement, as cairo provides no support to do so.
- PS output does not respect SOURCE_DATE_EPOCH.
- PS output does not support the
Creator
metadata key; however it supportstheTitle
key. - The following rcparams have no effect:
pdf.fonttype
(font type is selected by cairo internally),pdf.inheritcolor
(effectively alwaysFalse
),pdf.use14corefonts
(effectively alwaysFalse
),ps.fonttype
(font type is selected by cairo internally),ps.useafm
(effectively alwaysFalse
),svg.fonttype
(effectively always"path"
, seecairo issue #253),svg.hashsalt
,svg.id
.
Additionally, thequality
,optimize
, andprogressive
parameters tosavefig
, which have been removed in Matplotlib 3.5, are not supported.
- Cache eviction policy and persistent cache for
draw_path_collection
. - Use QtOpenGLWidget and the cairo-gl backend.
They are very slow (try runningexamples/mplot3d/wire3d_animation.py) andrender math poorly (trytitle(r"$\sqrt{2}$")
).
About
A (new) cairo backend for Matplotlib.
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors8
Uh oh!
There was an error while loading.Please reload this page.