Coverage for cc_modules/cc_plot.py : 92%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
3"""
4camcops_server/cc_modules/cc_plot.py
6===============================================================================
8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com).
10 This file is part of CamCOPS.
12 CamCOPS is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
17 CamCOPS is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
25===============================================================================
27**Plotting functions.**
29PROPER WAY TO USE MATPLOTLIB:
31- https://jbarillari.blogspot.co.uk/2009/09/threadsafety-and-matplotlibpylab.html?m=1
32- https://sjohannes.wordpress.com/2010/06/11/using-matplotlib-in-a-web-application/amp/
33- https://matplotlib.org/faq/howto_faq.html#howto-webapp
34- https://matplotlib.org/examples/api/agg_oo.html#api-agg-oo
36In summary: matplotlib is easy to use in a way that has global state, but that
37will break in a threading application. Using the Figure() API is safe. Thus:
39.. code-block:: python
41 from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
42 from matplotlib.figure import Figure
44 fig = Figure()
45 canvas = FigureCanvas(fig)
46 ax = fig.add_subplot(111)
47 ax.plot([1, 2, 3])
48 ax.set_title('hi mom')
49 ax.grid(True)
50 ax.set_xlabel('time')
51 ax.set_ylabel('volts')
52 canvas.print_figure('test')
54""" # noqa
56# =============================================================================
57# Basic imports
58# =============================================================================
60import atexit
61import logging
62import os
63import shutil
64import tempfile
66from cardinal_pythonlib.logs import BraceStyleAdapter
68log = BraceStyleAdapter(logging.getLogger(__name__))
71# =============================================================================
72# Constants
73# =============================================================================
75ENVVAR_HOME = "HOME"
76ENVVAR_MPLCONFIGDIR = "MPLCONFIGDIR"
78# =============================================================================
79# Import matplotlib
80# =============================================================================
82# We need to use os.environ, since per-request stuff won't be initialized yet.
83# That goes for anything that affects imports (to avoid the complexity of
84# delayed imports).
85if ENVVAR_MPLCONFIGDIR in os.environ:
86 # 1+2. Use a writable static directory (speeds pyplot loads hugely).
87 _mpl_config_dir = os.environ[ENVVAR_MPLCONFIGDIR]
88else:
89 # 1. Make a temporary directory (must be a directory per process, I'm sure)
90 _mpl_config_dir = tempfile.mkdtemp()
91 # 2. Ensure temporary directory is removed when this process exits.
92 atexit.register(lambda: shutil.rmtree(_mpl_config_dir, ignore_errors=True))
94# 3. Tell matplotlib about this directory prior to importing it
95# http://matplotlib.org/faq/environment_variables_faq.html
96os.environ[ENVVAR_MPLCONFIGDIR] = _mpl_config_dir
98# 4. Another nasty matplotlib hack
99# matplotlib.font_manager reads os.environ.get('HOME') directly, and
100# searches ~/.fonts for fonts. That's fine unless a user is calling with
101# sudo -u USER, leaving $HOME as it was but removing the permissions - then
102# matplotlib crashes out with e.g.
103# PermissionError: [Errno 13] Permission denied: '/home/rudolf/.fonts/SABOI___.TTF' # noqa
104# Note that an empty string won't help either, since the check is
105# "is not None".
106# You can't assign None to an os.environ member; see
107# http://stackoverflow.com/questions/3575165; do this:
108if ENVVAR_HOME in os.environ:
109 _old_home = os.environ[ENVVAR_HOME]
110 del os.environ[ENVVAR_HOME]
111else:
112 _old_home = None
114# 5. Import matplotlib
115log.debug("Importing matplotlib (can be slow) (MPLCONFIGDIR={})...",
116 _mpl_config_dir)
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 # noqa
133log.debug("... finished importing matplotlib")
135# REPLACED BY OO METHOD # # THEN DO e.g. # import matplotlib.pyplot as plt