Coverage for cc_modules/cc_plot.py: 91%
23 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
1"""
2camcops_server/cc_modules/cc_plot.py
4===============================================================================
6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CamCOPS.
11 CamCOPS is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
16 CamCOPS is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
24===============================================================================
26**Plotting functions.**
28PROPER WAY TO USE MATPLOTLIB:
30- https://jbarillari.blogspot.co.uk/2009/09/threadsafety-and-matplotlibpylab.html?m=1
31- https://sjohannes.wordpress.com/2010/06/11/using-matplotlib-in-a-web-application/amp/
32- https://matplotlib.org/faq/howto_faq.html#howto-webapp
33- https://matplotlib.org/examples/api/agg_oo.html#api-agg-oo
35In summary: matplotlib is easy to use in a way that has global state, but that
36will break in a threading application. Using the Figure() API is safe. Thus:
38.. code-block:: python
40 from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
41 from matplotlib.figure import Figure
43 fig = Figure()
44 canvas = FigureCanvas(fig)
45 ax = fig.add_subplot(111)
46 ax.plot([1, 2, 3])
47 ax.set_title('hi mom')
48 ax.grid(True)
49 ax.set_xlabel('time')
50 ax.set_ylabel('volts')
51 canvas.print_figure('test')
53""" # noqa
55# =============================================================================
56# Basic imports
57# =============================================================================
59import atexit
60import logging
61import os
62import shutil
63import tempfile
65from cardinal_pythonlib.logs import BraceStyleAdapter
67log = BraceStyleAdapter(logging.getLogger(__name__))
70# =============================================================================
71# Constants
72# =============================================================================
74ENVVAR_HOME = "HOME"
75ENVVAR_MPLCONFIGDIR = "MPLCONFIGDIR"
77# =============================================================================
78# Import matplotlib
79# =============================================================================
81# We need to use os.environ, since per-request stuff won't be initialized yet.
82# That goes for anything that affects imports (to avoid the complexity of
83# delayed imports).
84if ENVVAR_MPLCONFIGDIR in os.environ:
85 # 1+2. Use a writable static directory (speeds pyplot loads hugely).
86 _mpl_config_dir = os.environ[ENVVAR_MPLCONFIGDIR]
87else:
88 # 1. Make a temporary directory (must be a directory per process, I'm sure)
89 _mpl_config_dir = tempfile.mkdtemp()
90 # 2. Ensure temporary directory is removed when this process exits.
91 atexit.register(lambda: shutil.rmtree(_mpl_config_dir, ignore_errors=True))
93# 3. Tell matplotlib about this directory prior to importing it
94# http://matplotlib.org/faq/environment_variables_faq.html
95os.environ[ENVVAR_MPLCONFIGDIR] = _mpl_config_dir
97# 4. Another nasty matplotlib hack
98# matplotlib.font_manager reads os.environ.get('HOME') directly, and
99# searches ~/.fonts for fonts. That's fine unless a user is calling with
100# sudo -u USER, leaving $HOME as it was but removing the permissions - then
101# matplotlib crashes out with e.g.
102# PermissionError: [Errno 13] Permission denied: '/home/rudolf/.fonts/SABOI___.TTF' # noqa
103# Note that an empty string won't help either, since the check is
104# "is not None".
105# You can't assign None to an os.environ member; see
106# http://stackoverflow.com/questions/3575165; do this:
107if ENVVAR_HOME in os.environ:
108 _old_home = os.environ[ENVVAR_HOME]
109 del os.environ[ENVVAR_HOME]
110else:
111 _old_home = None
113# 5. Import matplotlib
114log.debug(
115 "Importing matplotlib (can be slow) (MPLCONFIGDIR={})...", _mpl_config_dir
116)
117# noinspection PyUnresolvedReferences
118import matplotlib # noqa: E402, F401
120# 6. Restore $HOME
121if _old_home is not None:
122 os.environ[ENVVAR_HOME] = _old_home
124# 7. Set the backend
125# REPLACED BY OO METHOD # matplotlib.use("Agg") # also the default backend
126# ... http://matplotlib.org/faq/usage_faq.html#what-is-a-backend
127# ... http://matplotlib.org/faq/howto_faq.html
128# matplotlib.use("cairo") # cairo backend corrupts some SVG figures
130# Load this once so we can tell the user we're importing it and it's slow
131# REPLACED BY OO METHOD # import matplotlib.pyplot
133log.debug("... finished importing matplotlib")
135# REPLACED BY OO METHOD # # THEN DO e.g. # import matplotlib.pyplot as plt