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