Coverage for cc_modules/cc_plot.py: 91%

1"""

2 camcops_server/cc_modules/cc_plot.py

3

4===============================================================================

5

6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

8

9 This file is part of CamCOPS.

10

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.

15

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.

20

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/>.

23

24===============================================================================

25

26**Plotting functions.**

27

28 PROPER WAY TO USE MATPLOTLIB:

29

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

34

35 In summary: matplotlib is easy to use in a way that has global state, but that

36 will break in a threading application. Using the Figure() API is safe. Thus:

37

38.. code-block:: python

39

40 from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas

41 from matplotlib.figure import Figure

42

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')

52

53""" # noqa

54

55# =============================================================================

56# Basic imports

57# =============================================================================

58

59 import atexit

60 import logging

61 import os

62 import shutil

63 import tempfile

64

65 from cardinal_pythonlib.logs import BraceStyleAdapter

66

67 log = BraceStyleAdapter(logging.getLogger(__name__))

68

69

70# =============================================================================

71# Constants

72# =============================================================================

73

74 ENVVAR_HOME = "HOME"

75 ENVVAR_MPLCONFIGDIR = "MPLCONFIGDIR"

76

77# =============================================================================

78# Import matplotlib

79# =============================================================================

80

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).

84 if 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]

87 else:

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))

92

93# 3. Tell matplotlib about this directory prior to importing it

94# http://matplotlib.org/faq/environment_variables_faq.html

95 os.environ[ENVVAR_MPLCONFIGDIR] = _mpl_config_dir

96

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:

107 if ENVVAR_HOME in os.environ:

108 _old_home = os.environ[ENVVAR_HOME]

109 del os.environ[ENVVAR_HOME]

110 else:

111 _old_home = None

112

113# 5. Import matplotlib

114 log.debug(

115 "Importing matplotlib (can be slow) (MPLCONFIGDIR={})...", _mpl_config_dir

116)

117# noinspection PyUnresolvedReferences

118 import matplotlib # noqa: E402, F401

119

120# 6. Restore $HOME

121 if _old_home is not None:

122 os.environ[ENVVAR_HOME] = _old_home

123

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

129

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

132

133 log.debug("... finished importing matplotlib")

134

135# REPLACED BY OO METHOD # # THEN DO e.g. # import matplotlib.pyplot as plt