• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

domdfcoding / domplotlib / 15188046115

22 May 2025 01:35PM UTC coverage: 92.683%. Remained the same
15188046115

push

github

domdfcoding
Drop support for Python 3.6

76 of 82 relevant lines covered (92.68%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.8
/domplotlib/__init__.py
1
#!/usr/bin/env python3
2
#
3
#  __init__.py
4
"""
5
Dom's extensions to matplotlib.
6
"""
7
#
8
#  Copyright © 2020 Dominic Davis-Foster <dominic@davis-foster.co.uk>
9
#
10
#  Permission is hereby granted, free of charge, to any person obtaining a copy
11
#  of this software and associated documentation files (the "Software"), to deal
12
#  in the Software without restriction, including without limitation the rights
13
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
#  copies of the Software, and to permit persons to whom the Software is
15
#  furnished to do so, subject to the following conditions:
16
#
17
#  The above copyright notice and this permission notice shall be included in all
18
#  copies or substantial portions of the Software.
19
#
20
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
#  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
24
#  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
25
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
26
#  OR OTHER DEALINGS IN THE SOFTWARE.
27
#
28
#  "save_svg" based on matplotlib
29
#  |  1. This LICENSE AGREEMENT is between the Matplotlib Development Team
30
#  |  ("MDT"), and the Individual or Organization ("Licensee") accessing and
31
#  |  otherwise using matplotlib software in source or binary form and its
32
#  |  associated documentation.
33
#  |
34
#  |  2. Subject to the terms and conditions of this License Agreement, MDT
35
#  |  hereby grants Licensee a nonexclusive, royalty-free, world-wide license
36
#  |  to reproduce, analyze, test, perform and/or display publicly, prepare
37
#  |  derivative works, distribute, and otherwise use matplotlib
38
#  |  alone or in any derivative version, provided, however, that MDT's
39
#  |  License Agreement and MDT's notice of copyright, i.e., "Copyright (c)
40
#  |  2012- Matplotlib Development Team; All Rights Reserved" are retained in
41
#  |  matplotlib  alone or in any derivative version prepared by
42
#  |  Licensee.
43
#  |
44
#  |  3. In the event Licensee prepares a derivative work that is based on or
45
#  |  incorporates matplotlib or any part thereof, and wants to
46
#  |  make the derivative work available to others as provided herein, then
47
#  |  Licensee hereby agrees to include in any such work a brief summary of
48
#  |  the changes made to matplotlib .
49
#  |
50
#  |  4. MDT is making matplotlib available to Licensee on an "AS
51
#  |  IS" basis.  MDT MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
52
#  |  IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, MDT MAKES NO AND
53
#  |  DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
54
#  |  FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF MATPLOTLIB
55
#  |  WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
56
#  |
57
#  |  5. MDT SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF MATPLOTLIB
58
#  |  FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR
59
#  |  LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING
60
#  |  MATPLOTLIB , OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF
61
#  |  THE POSSIBILITY THEREOF.
62
#  |
63
#  |  6. This License Agreement will automatically terminate upon a material
64
#  |  breach of its terms and conditions.
65
#  |
66
#  |  7. Nothing in this License Agreement shall be deemed to create any
67
#  |  relationship of agency, partnership, or joint venture between MDT and
68
#  |  Licensee.  This License Agreement does not grant permission to use MDT
69
#  |  trademarks or trade name in a trademark sense to endorse or promote
70
#  |  products or services of Licensee, or any third party.
71
#  |
72
#  |  8. By copying, installing or otherwise using matplotlib ,
73
#  |  Licensee agrees to be bound by the terms and conditions of this License
74
#  |  Agreement.
75
#
76

77
# stdlib
78
import itertools
1✔
79
from io import StringIO
1✔
80
from typing import IO, Iterable, Optional, Tuple, TypeVar, Union
1✔
81

82
# 3rd party
83
from domdf_python_tools.iterative import chunks
1✔
84
from domdf_python_tools.pagesizes import PageSize
1✔
85
from domdf_python_tools.paths import PathPlus, clean_writer
1✔
86
from domdf_python_tools.typing import PathLike
1✔
87
from matplotlib.artist import Artist  # type: ignore[import]
1✔
88
from matplotlib.axes import Axes  # type: ignore[import]
1✔
89
from matplotlib.figure import Figure  # type: ignore[import]
1✔
90
from matplotlib.legend import Legend  # type: ignore[import]
1✔
91
from typing_extensions import Literal
1✔
92

93
__all__ = ["create_figure", "horizontal_legend", "save_svg", "transpose"]
1✔
94

95
__author__: str = "Dominic Davis-Foster"
1✔
96
__copyright__: str = "2020 Dominic Davis-Foster"
1✔
97
__license__: str = "MIT License"
1✔
98
__version__: str = "0.5.0"
1✔
99
__email__: str = "dominic@davis-foster.co.uk"
1✔
100

101
_T = TypeVar("_T")
1✔
102

103

104
def save_svg(
1✔
105
                figure: Figure,
106
                fname: Union[PathLike, IO],
107
                *,
108
                dpi: Union[float, Literal["figure"], None] = None,
109
                facecolor: Union[str, Literal["auto"]] = 'w',
110
                edgecolor: Union[str, Literal["auto"]] = 'w',
111
                orientation: Literal["portrait", "landscape"] = "portrait",
112
                transparent: bool = False,
113
                bbox_inches: Optional[str] = None,
114
                pad_inches: float = 0.1,
115
                **kwargs,
116
                ) -> None:
117
        r"""
118
        Save the given figure as an SVG.
119

120
        :param figure:
121
        :param fname: The file to save the SVG as.
122
                If ``format`` is set, it determines the output format, and the file is saved as ``fname``.
123
                Note that ``fname`` is used verbatim, and there is no attempt to make the extension,
124
                if any, of ``fname`` match ``format``, and no extension is appended.
125

126
                If ``format`` is not set, then the format is inferred from the extension of ``fname``, if there is one.
127

128
        :param dpi: The resolution in dots per inch. If ``'figure'``, use the figure's dpi value.
129
        :param facecolor: The facecolor of the figure. If ``'auto'``, use the current figure facecolor.
130
        :param edgecolor: The edgecolor of the figure.  If ``'auto'``, use the current figure edgecolor.
131
        :param orientation: Currently only supported by the postscript backend.
132

133
        :param transparent: If :py:obj:`True`, the axes patches will all be transparent;
134
                the figure patch will also be transparent unless ``facecolor`` and/or ``edgecolor`` are specified.
135
                This is useful, for example, for displaying a plot on top of a colored background on a web page.
136
                The transparency of these patches will be restored to their original values upon exit of this function.
137

138
        :param bbox_inches: Bounding box in inches: only the given portion of the figure is saved.
139
                If 'tight', try to figure out the tight bbox of the figure.
140

141
        :param pad_inches: Amount of padding around the figure when bbox_inches is 'tight'.
142

143
        :param \*\*kwargs: Additional keyword arguments passed to :meth:`~.Figure.savefig`.
144
        """
145

146
        buf = StringIO()
1✔
147

148
        figure.savefig(
1✔
149
                        fname=buf,
150
                        format="svg",
151
                        dpi=dpi,
152
                        facecolor=facecolor,
153
                        edgecolor=edgecolor,
154
                        orientation=orientation,
155
                        transparent=transparent,
156
                        bbox_inches=bbox_inches,
157
                        pad_inches=pad_inches,
158
                        **kwargs,
159
                        )
160

161
        # need this if 'transparent=True' to reset colors
162
        figure.canvas.draw_idle()
1✔
163

164
        if isinstance(fname, IO):
1✔
165
                clean_writer(buf.getvalue(), fname)
×
166
        else:
167
                PathPlus(fname).write_clean(buf.getvalue())
1✔
168

169

170
def transpose(iterable: Iterable[_T], ncol: int) -> Iterable[_T]:
1✔
171
        """
172
        Transposes the contents of ``iterable`` so they are ordered right to left rather than top to bottom.
173

174
        :param iterable:
175
        :param ncol:
176

177
        :returns: An :class:`~typing.Iterable` contaning elements of the same type as ``iterable``.
178
        """
179

180
        return itertools.chain.from_iterable(itertools.zip_longest(*chunks(tuple(iterable), ncol)))
1✔
181

182

183
def horizontal_legend(
1✔
184
                fig: Figure,
185
                handles: Optional[Iterable[Artist]] = None,
186
                labels: Optional[Iterable[str]] = None,
187
                *,
188
                ncol: int = 1,
189
                **kwargs,
190
                ) -> Legend:
191
        """
192
        Place a legend on the figure, with the items arranged to read right to left rather than top to bottom.
193

194
        :param fig: The figure to plot the legend on.
195
        :param handles:
196
        :param labels:
197
        :param ncol: The number of columns in the legend.
198
        :param kwargs: Addition keyword arguments passed to :meth:`matplotlib.figure.Figure.legend`.
199
        """
200

201
        if handles is None and labels is None:
1✔
202
                handles, labels = fig.axes[0].get_legend_handles_labels()
1✔
203

204
        # Rearrange legend items to read right to left rather than top to bottom.
205
        if handles:
1✔
206
                handles = list(filter(None, transpose(handles, ncol)))
1✔
207
        if labels:
1✔
208
                labels = list(filter(None, transpose(labels, ncol)))
1✔
209

210
        return fig.legend(handles, labels, ncol=ncol, **kwargs)
1✔
211

212

213
def create_figure(
1✔
214
                pagesize: PageSize,
215
                left: float = 0.2,
216
                bottom: float = 0.14,
217
                right: float = 0.025,
218
                top: float = 0.13,
219
                ) -> Tuple[Figure, Axes]:
220
        """
221
        Creates a figure with the given margins,
222
        and returns a tuple of the figure and its axes.
223

224
        :param pagesize:
225
        :param left: Left margin
226
        :param bottom: Bottom margin
227
        :param right: Right margin
228
        :param top: Top margin
229
        """  # noqa: D400
230

231
        # 3rd party
232
        from matplotlib import pyplot  # type: ignore[import]
×
233

234
        # Import here to avoid clobbering theme and backend choices.
235

236
        fig = pyplot.figure(figsize=pagesize)
×
237

238
        # [left, bottom, width, height]
239
        ax = fig.add_axes([left, bottom, 1 - left - right, 1 - top - bottom])
×
240

241
        return fig, ax
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc