Coverage for /usr/lib/python3/dist-packages/matplotlib/cbook/__init__.py: 43%

914 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1""" 

2A collection of utility functions and classes. Originally, many 

3(but not all) were from the Python Cookbook -- hence the name cbook. 

4 

5This module is safe to import from anywhere within Matplotlib; 

6it imports Matplotlib only at runtime. 

7""" 

8 

9import collections 

10import collections.abc 

11import contextlib 

12import functools 

13import gzip 

14import itertools 

15import math 

16import operator 

17import os 

18from pathlib import Path 

19import shlex 

20import subprocess 

21import sys 

22import time 

23import traceback 

24import types 

25import weakref 

26 

27import numpy as np 

28 

29import matplotlib 

30from matplotlib import _api, _c_internal_utils 

31 

32 

33@_api.caching_module_getattr 

34class __getattr__: 

35 # module-level deprecations 

36 MatplotlibDeprecationWarning = _api.deprecated( 

37 "3.6", obj_type="", 

38 alternative="matplotlib.MatplotlibDeprecationWarning")( 

39 property(lambda self: _api.deprecation.MatplotlibDeprecationWarning)) 

40 mplDeprecation = _api.deprecated( 

41 "3.6", obj_type="", 

42 alternative="matplotlib.MatplotlibDeprecationWarning")( 

43 property(lambda self: _api.deprecation.MatplotlibDeprecationWarning)) 

44 

45 

46def _get_running_interactive_framework(): 

47 """ 

48 Return the interactive framework whose event loop is currently running, if 

49 any, or "headless" if no event loop can be started, or None. 

50 

51 Returns 

52 ------- 

53 Optional[str] 

54 One of the following values: "qt", "gtk3", "gtk4", "wx", "tk", 

55 "macosx", "headless", ``None``. 

56 """ 

57 # Use ``sys.modules.get(name)`` rather than ``name in sys.modules`` as 

58 # entries can also have been explicitly set to None. 

59 QtWidgets = ( 

60 sys.modules.get("PyQt6.QtWidgets") 

61 or sys.modules.get("PySide6.QtWidgets") 

62 or sys.modules.get("PyQt5.QtWidgets") 

63 or sys.modules.get("PySide2.QtWidgets") 

64 ) 

65 if QtWidgets and QtWidgets.QApplication.instance(): 

66 return "qt" 

67 Gtk = sys.modules.get("gi.repository.Gtk") 

68 if Gtk: 

69 if Gtk.MAJOR_VERSION == 4: 

70 from gi.repository import GLib 

71 if GLib.main_depth(): 

72 return "gtk4" 

73 if Gtk.MAJOR_VERSION == 3 and Gtk.main_level(): 

74 return "gtk3" 

75 wx = sys.modules.get("wx") 

76 if wx and wx.GetApp(): 

77 return "wx" 

78 tkinter = sys.modules.get("tkinter") 

79 if tkinter: 

80 codes = {tkinter.mainloop.__code__, tkinter.Misc.mainloop.__code__} 

81 for frame in sys._current_frames().values(): 

82 while frame: 

83 if frame.f_code in codes: 

84 return "tk" 

85 frame = frame.f_back 

86 macosx = sys.modules.get("matplotlib.backends._macosx") 

87 if macosx and macosx.event_loop_is_running(): 

88 return "macosx" 

89 if not _c_internal_utils.display_is_valid(): 

90 return "headless" 

91 return None 

92 

93 

94def _exception_printer(exc): 

95 if _get_running_interactive_framework() in ["headless", None]: 

96 raise exc 

97 else: 

98 traceback.print_exc() 

99 

100 

101class _StrongRef: 

102 """ 

103 Wrapper similar to a weakref, but keeping a strong reference to the object. 

104 """ 

105 

106 def __init__(self, obj): 

107 self._obj = obj 

108 

109 def __call__(self): 

110 return self._obj 

111 

112 def __eq__(self, other): 

113 return isinstance(other, _StrongRef) and self._obj == other._obj 

114 

115 def __hash__(self): 

116 return hash(self._obj) 

117 

118 

119def _weak_or_strong_ref(func, callback): 

120 """ 

121 Return a `WeakMethod` wrapping *func* if possible, else a `_StrongRef`. 

122 """ 

123 try: 

124 return weakref.WeakMethod(func, callback) 

125 except TypeError: 

126 return _StrongRef(func) 

127 

128 

129class CallbackRegistry: 

130 """ 

131 Handle registering, processing, blocking, and disconnecting 

132 for a set of signals and callbacks: 

133 

134 >>> def oneat(x): 

135 ... print('eat', x) 

136 >>> def ondrink(x): 

137 ... print('drink', x) 

138 

139 >>> from matplotlib.cbook import CallbackRegistry 

140 >>> callbacks = CallbackRegistry() 

141 

142 >>> id_eat = callbacks.connect('eat', oneat) 

143 >>> id_drink = callbacks.connect('drink', ondrink) 

144 

145 >>> callbacks.process('drink', 123) 

146 drink 123 

147 >>> callbacks.process('eat', 456) 

148 eat 456 

149 >>> callbacks.process('be merry', 456) # nothing will be called 

150 

151 >>> callbacks.disconnect(id_eat) 

152 >>> callbacks.process('eat', 456) # nothing will be called 

153 

154 >>> with callbacks.blocked(signal='drink'): 

155 ... callbacks.process('drink', 123) # nothing will be called 

156 >>> callbacks.process('drink', 123) 

157 drink 123 

158 

159 In practice, one should always disconnect all callbacks when they are 

160 no longer needed to avoid dangling references (and thus memory leaks). 

161 However, real code in Matplotlib rarely does so, and due to its design, 

162 it is rather difficult to place this kind of code. To get around this, 

163 and prevent this class of memory leaks, we instead store weak references 

164 to bound methods only, so when the destination object needs to die, the 

165 CallbackRegistry won't keep it alive. 

166 

167 Parameters 

168 ---------- 

169 exception_handler : callable, optional 

170 If not None, *exception_handler* must be a function that takes an 

171 `Exception` as single parameter. It gets called with any `Exception` 

172 raised by the callbacks during `CallbackRegistry.process`, and may 

173 either re-raise the exception or handle it in another manner. 

174 

175 The default handler prints the exception (with `traceback.print_exc`) if 

176 an interactive event loop is running; it re-raises the exception if no 

177 interactive event loop is running. 

178 

179 signals : list, optional 

180 If not None, *signals* is a list of signals that this registry handles: 

181 attempting to `process` or to `connect` to a signal not in the list 

182 throws a `ValueError`. The default, None, does not restrict the 

183 handled signals. 

184 """ 

185 

186 # We maintain two mappings: 

187 # callbacks: signal -> {cid -> weakref-to-callback} 

188 # _func_cid_map: signal -> {weakref-to-callback -> cid} 

189 

190 def __init__(self, exception_handler=_exception_printer, *, signals=None): 

191 self._signals = None if signals is None else list(signals) # Copy it. 

192 self.exception_handler = exception_handler 

193 self.callbacks = {} 

194 self._cid_gen = itertools.count() 

195 self._func_cid_map = {} 

196 # A hidden variable that marks cids that need to be pickled. 

197 self._pickled_cids = set() 

198 

199 def __getstate__(self): 

200 return { 

201 **vars(self), 

202 # In general, callbacks may not be pickled, so we just drop them, 

203 # unless directed otherwise by self._pickled_cids. 

204 "callbacks": {s: {cid: proxy() for cid, proxy in d.items() 

205 if cid in self._pickled_cids} 

206 for s, d in self.callbacks.items()}, 

207 # It is simpler to reconstruct this from callbacks in __setstate__. 

208 "_func_cid_map": None, 

209 } 

210 

211 def __setstate__(self, state): 

212 vars(self).update(state) 

213 self.callbacks = { 

214 s: {cid: _weak_or_strong_ref(func, self._remove_proxy) 

215 for cid, func in d.items()} 

216 for s, d in self.callbacks.items()} 

217 self._func_cid_map = { 

218 s: {proxy: cid for cid, proxy in d.items()} 

219 for s, d in self.callbacks.items()} 

220 

221 def connect(self, signal, func): 

222 """Register *func* to be called when signal *signal* is generated.""" 

223 if signal == "units finalize": 

224 _api.warn_deprecated( 

225 "3.5", name=signal, obj_type="signal", alternative="units") 

226 if self._signals is not None: 

227 _api.check_in_list(self._signals, signal=signal) 

228 self._func_cid_map.setdefault(signal, {}) 

229 proxy = _weak_or_strong_ref(func, self._remove_proxy) 

230 if proxy in self._func_cid_map[signal]: 

231 return self._func_cid_map[signal][proxy] 

232 cid = next(self._cid_gen) 

233 self._func_cid_map[signal][proxy] = cid 

234 self.callbacks.setdefault(signal, {}) 

235 self.callbacks[signal][cid] = proxy 

236 return cid 

237 

238 def _connect_picklable(self, signal, func): 

239 """ 

240 Like `.connect`, but the callback is kept when pickling/unpickling. 

241 

242 Currently internal-use only. 

243 """ 

244 cid = self.connect(signal, func) 

245 self._pickled_cids.add(cid) 

246 return cid 

247 

248 # Keep a reference to sys.is_finalizing, as sys may have been cleared out 

249 # at that point. 

250 def _remove_proxy(self, proxy, *, _is_finalizing=sys.is_finalizing): 

251 if _is_finalizing(): 

252 # Weakrefs can't be properly torn down at that point anymore. 

253 return 

254 for signal, proxy_to_cid in list(self._func_cid_map.items()): 

255 cid = proxy_to_cid.pop(proxy, None) 

256 if cid is not None: 

257 del self.callbacks[signal][cid] 

258 self._pickled_cids.discard(cid) 

259 break 

260 else: 

261 # Not found 

262 return 

263 # Clean up empty dicts 

264 if len(self.callbacks[signal]) == 0: 

265 del self.callbacks[signal] 

266 del self._func_cid_map[signal] 

267 

268 def disconnect(self, cid): 

269 """ 

270 Disconnect the callback registered with callback id *cid*. 

271 

272 No error is raised if such a callback does not exist. 

273 """ 

274 self._pickled_cids.discard(cid) 

275 # Clean up callbacks 

276 for signal, cid_to_proxy in list(self.callbacks.items()): 

277 proxy = cid_to_proxy.pop(cid, None) 

278 if proxy is not None: 

279 break 

280 else: 

281 # Not found 

282 return 

283 

284 proxy_to_cid = self._func_cid_map[signal] 

285 for current_proxy, current_cid in list(proxy_to_cid.items()): 

286 if current_cid == cid: 

287 assert proxy is current_proxy 

288 del proxy_to_cid[current_proxy] 

289 # Clean up empty dicts 

290 if len(self.callbacks[signal]) == 0: 

291 del self.callbacks[signal] 

292 del self._func_cid_map[signal] 

293 

294 def process(self, s, *args, **kwargs): 

295 """ 

296 Process signal *s*. 

297 

298 All of the functions registered to receive callbacks on *s* will be 

299 called with ``*args`` and ``**kwargs``. 

300 """ 

301 if self._signals is not None: 

302 _api.check_in_list(self._signals, signal=s) 

303 for cid, ref in list(self.callbacks.get(s, {}).items()): 

304 func = ref() 

305 if func is not None: 

306 try: 

307 func(*args, **kwargs) 

308 # this does not capture KeyboardInterrupt, SystemExit, 

309 # and GeneratorExit 

310 except Exception as exc: 

311 if self.exception_handler is not None: 

312 self.exception_handler(exc) 

313 else: 

314 raise 

315 

316 @contextlib.contextmanager 

317 def blocked(self, *, signal=None): 

318 """ 

319 Block callback signals from being processed. 

320 

321 A context manager to temporarily block/disable callback signals 

322 from being processed by the registered listeners. 

323 

324 Parameters 

325 ---------- 

326 signal : str, optional 

327 The callback signal to block. The default is to block all signals. 

328 """ 

329 orig = self.callbacks 

330 try: 

331 if signal is None: 

332 # Empty out the callbacks 

333 self.callbacks = {} 

334 else: 

335 # Only remove the specific signal 

336 self.callbacks = {k: orig[k] for k in orig if k != signal} 

337 yield 

338 finally: 

339 self.callbacks = orig 

340 

341 

342class silent_list(list): 

343 """ 

344 A list with a short ``repr()``. 

345 

346 This is meant to be used for a homogeneous list of artists, so that they 

347 don't cause long, meaningless output. 

348 

349 Instead of :: 

350 

351 [<matplotlib.lines.Line2D object at 0x7f5749fed3c8>, 

352 <matplotlib.lines.Line2D object at 0x7f5749fed4e0>, 

353 <matplotlib.lines.Line2D object at 0x7f5758016550>] 

354 

355 one will get :: 

356 

357 <a list of 3 Line2D objects> 

358 

359 If ``self.type`` is None, the type name is obtained from the first item in 

360 the list (if any). 

361 """ 

362 

363 def __init__(self, type, seq=None): 

364 self.type = type 

365 if seq is not None: 

366 self.extend(seq) 

367 

368 def __repr__(self): 

369 if self.type is not None or len(self) != 0: 

370 tp = self.type if self.type is not None else type(self[0]).__name__ 

371 return f"<a list of {len(self)} {tp} objects>" 

372 else: 

373 return "<an empty list>" 

374 

375 

376def _local_over_kwdict( 

377 local_var, kwargs, *keys, 

378 warning_cls=_api.MatplotlibDeprecationWarning): 

379 out = local_var 

380 for key in keys: 

381 kwarg_val = kwargs.pop(key, None) 

382 if kwarg_val is not None: 

383 if out is None: 

384 out = kwarg_val 

385 else: 

386 _api.warn_external(f'"{key}" keyword argument will be ignored', 

387 warning_cls) 

388 return out 

389 

390 

391def strip_math(s): 

392 """ 

393 Remove latex formatting from mathtext. 

394 

395 Only handles fully math and fully non-math strings. 

396 """ 

397 if len(s) >= 2 and s[0] == s[-1] == "$": 

398 s = s[1:-1] 

399 for tex, plain in [ 

400 (r"\times", "x"), # Specifically for Formatter support. 

401 (r"\mathdefault", ""), 

402 (r"\rm", ""), 

403 (r"\cal", ""), 

404 (r"\tt", ""), 

405 (r"\it", ""), 

406 ("\\", ""), 

407 ("{", ""), 

408 ("}", ""), 

409 ]: 

410 s = s.replace(tex, plain) 

411 return s 

412 

413 

414def _strip_comment(s): 

415 """Strip everything from the first unquoted #.""" 

416 pos = 0 

417 while True: 

418 quote_pos = s.find('"', pos) 

419 hash_pos = s.find('#', pos) 

420 if quote_pos < 0: 

421 without_comment = s if hash_pos < 0 else s[:hash_pos] 

422 return without_comment.strip() 

423 elif 0 <= hash_pos < quote_pos: 

424 return s[:hash_pos].strip() 

425 else: 

426 closing_quote_pos = s.find('"', quote_pos + 1) 

427 if closing_quote_pos < 0: 

428 raise ValueError( 

429 f"Missing closing quote in: {s!r}. If you need a double-" 

430 'quote inside a string, use escaping: e.g. "the \" char"') 

431 pos = closing_quote_pos + 1 # behind closing quote 

432 

433 

434def is_writable_file_like(obj): 

435 """Return whether *obj* looks like a file object with a *write* method.""" 

436 return callable(getattr(obj, 'write', None)) 

437 

438 

439def file_requires_unicode(x): 

440 """ 

441 Return whether the given writable file-like object requires Unicode to be 

442 written to it. 

443 """ 

444 try: 

445 x.write(b'') 

446 except TypeError: 

447 return True 

448 else: 

449 return False 

450 

451 

452def to_filehandle(fname, flag='r', return_opened=False, encoding=None): 

453 """ 

454 Convert a path to an open file handle or pass-through a file-like object. 

455 

456 Consider using `open_file_cm` instead, as it allows one to properly close 

457 newly created file objects more easily. 

458 

459 Parameters 

460 ---------- 

461 fname : str or path-like or file-like 

462 If `str` or `os.PathLike`, the file is opened using the flags specified 

463 by *flag* and *encoding*. If a file-like object, it is passed through. 

464 flag : str, default: 'r' 

465 Passed as the *mode* argument to `open` when *fname* is `str` or 

466 `os.PathLike`; ignored if *fname* is file-like. 

467 return_opened : bool, default: False 

468 If True, return both the file object and a boolean indicating whether 

469 this was a new file (that the caller needs to close). If False, return 

470 only the new file. 

471 encoding : str or None, default: None 

472 Passed as the *mode* argument to `open` when *fname* is `str` or 

473 `os.PathLike`; ignored if *fname* is file-like. 

474 

475 Returns 

476 ------- 

477 fh : file-like 

478 opened : bool 

479 *opened* is only returned if *return_opened* is True. 

480 """ 

481 if isinstance(fname, os.PathLike): 

482 fname = os.fspath(fname) 

483 if isinstance(fname, str): 

484 if fname.endswith('.gz'): 

485 fh = gzip.open(fname, flag) 

486 elif fname.endswith('.bz2'): 

487 # python may not be compiled with bz2 support, 

488 # bury import until we need it 

489 import bz2 

490 fh = bz2.BZ2File(fname, flag) 

491 else: 

492 fh = open(fname, flag, encoding=encoding) 

493 opened = True 

494 elif hasattr(fname, 'seek'): 

495 fh = fname 

496 opened = False 

497 else: 

498 raise ValueError('fname must be a PathLike or file handle') 

499 if return_opened: 

500 return fh, opened 

501 return fh 

502 

503 

504def open_file_cm(path_or_file, mode="r", encoding=None): 

505 r"""Pass through file objects and context-manage path-likes.""" 

506 fh, opened = to_filehandle(path_or_file, mode, True, encoding) 

507 return fh if opened else contextlib.nullcontext(fh) 

508 

509 

510def is_scalar_or_string(val): 

511 """Return whether the given object is a scalar or string like.""" 

512 return isinstance(val, str) or not np.iterable(val) 

513 

514 

515def get_sample_data(fname, asfileobj=True, *, np_load=False): 

516 """ 

517 Return a sample data file. *fname* is a path relative to the 

518 :file:`mpl-data/sample_data` directory. If *asfileobj* is `True` 

519 return a file object, otherwise just a file path. 

520 

521 Sample data files are stored in the 'mpl-data/sample_data' directory within 

522 the Matplotlib package. 

523 

524 If the filename ends in .gz, the file is implicitly ungzipped. If the 

525 filename ends with .npy or .npz, *asfileobj* is True, and *np_load* is 

526 True, the file is loaded with `numpy.load`. *np_load* currently defaults 

527 to False but will default to True in a future release. 

528 """ 

529 path = _get_data_path('sample_data', fname) 

530 if asfileobj: 

531 suffix = path.suffix.lower() 

532 if suffix == '.gz': 

533 return gzip.open(path) 

534 elif suffix in ['.npy', '.npz']: 

535 if np_load: 

536 return np.load(path) 

537 else: 

538 _api.warn_deprecated( 

539 "3.3", message="In a future release, get_sample_data " 

540 "will automatically load numpy arrays. Set np_load to " 

541 "True to get the array and suppress this warning. Set " 

542 "asfileobj to False to get the path to the data file and " 

543 "suppress this warning.") 

544 return path.open('rb') 

545 elif suffix in ['.csv', '.xrc', '.txt']: 

546 return path.open('r') 

547 else: 

548 return path.open('rb') 

549 else: 

550 return str(path) 

551 

552 

553def _get_data_path(*args): 

554 """ 

555 Return the `pathlib.Path` to a resource file provided by Matplotlib. 

556 

557 ``*args`` specify a path relative to the base data path. 

558 """ 

559 return Path(matplotlib.get_data_path(), *args) 

560 

561 

562def flatten(seq, scalarp=is_scalar_or_string): 

563 """ 

564 Return a generator of flattened nested containers. 

565 

566 For example: 

567 

568 >>> from matplotlib.cbook import flatten 

569 >>> l = (('John', ['Hunter']), (1, 23), [[([42, (5, 23)], )]]) 

570 >>> print(list(flatten(l))) 

571 ['John', 'Hunter', 1, 23, 42, 5, 23] 

572 

573 By: Composite of Holger Krekel and Luther Blissett 

574 From: https://code.activestate.com/recipes/121294/ 

575 and Recipe 1.12 in cookbook 

576 """ 

577 for item in seq: 

578 if scalarp(item) or item is None: 

579 yield item 

580 else: 

581 yield from flatten(item, scalarp) 

582 

583 

584@_api.deprecated("3.6", alternative="functools.lru_cache") 

585class maxdict(dict): 

586 """ 

587 A dictionary with a maximum size. 

588 

589 Notes 

590 ----- 

591 This doesn't override all the relevant methods to constrain the size, 

592 just ``__setitem__``, so use with caution. 

593 """ 

594 

595 def __init__(self, maxsize): 

596 super().__init__() 

597 self.maxsize = maxsize 

598 

599 def __setitem__(self, k, v): 

600 super().__setitem__(k, v) 

601 while len(self) >= self.maxsize: 

602 del self[next(iter(self))] 

603 

604 

605class Stack: 

606 """ 

607 Stack of elements with a movable cursor. 

608 

609 Mimics home/back/forward in a web browser. 

610 """ 

611 

612 def __init__(self, default=None): 

613 self.clear() 

614 self._default = default 

615 

616 def __call__(self): 

617 """Return the current element, or None.""" 

618 if not self._elements: 

619 return self._default 

620 else: 

621 return self._elements[self._pos] 

622 

623 def __len__(self): 

624 return len(self._elements) 

625 

626 def __getitem__(self, ind): 

627 return self._elements[ind] 

628 

629 def forward(self): 

630 """Move the position forward and return the current element.""" 

631 self._pos = min(self._pos + 1, len(self._elements) - 1) 

632 return self() 

633 

634 def back(self): 

635 """Move the position back and return the current element.""" 

636 if self._pos > 0: 

637 self._pos -= 1 

638 return self() 

639 

640 def push(self, o): 

641 """ 

642 Push *o* to the stack at current position. Discard all later elements. 

643 

644 *o* is returned. 

645 """ 

646 self._elements = self._elements[:self._pos + 1] + [o] 

647 self._pos = len(self._elements) - 1 

648 return self() 

649 

650 def home(self): 

651 """ 

652 Push the first element onto the top of the stack. 

653 

654 The first element is returned. 

655 """ 

656 if not self._elements: 

657 return 

658 self.push(self._elements[0]) 

659 return self() 

660 

661 def empty(self): 

662 """Return whether the stack is empty.""" 

663 return len(self._elements) == 0 

664 

665 def clear(self): 

666 """Empty the stack.""" 

667 self._pos = -1 

668 self._elements = [] 

669 

670 def bubble(self, o): 

671 """ 

672 Raise all references of *o* to the top of the stack, and return it. 

673 

674 Raises 

675 ------ 

676 ValueError 

677 If *o* is not in the stack. 

678 """ 

679 if o not in self._elements: 

680 raise ValueError('Given element not contained in the stack') 

681 old_elements = self._elements.copy() 

682 self.clear() 

683 top_elements = [] 

684 for elem in old_elements: 

685 if elem == o: 

686 top_elements.append(elem) 

687 else: 

688 self.push(elem) 

689 for _ in top_elements: 

690 self.push(o) 

691 return o 

692 

693 def remove(self, o): 

694 """ 

695 Remove *o* from the stack. 

696 

697 Raises 

698 ------ 

699 ValueError 

700 If *o* is not in the stack. 

701 """ 

702 if o not in self._elements: 

703 raise ValueError('Given element not contained in the stack') 

704 old_elements = self._elements.copy() 

705 self.clear() 

706 for elem in old_elements: 

707 if elem != o: 

708 self.push(elem) 

709 

710 

711@_api.deprecated("3.5", alternative="psutil.virtual_memory") 

712def report_memory(i=0): # argument may go away 

713 """Return the memory consumed by the process.""" 

714 def call(command, os_name): 

715 try: 

716 return subprocess.check_output(command) 

717 except subprocess.CalledProcessError as err: 

718 raise NotImplementedError( 

719 "report_memory works on %s only if " 

720 "the '%s' program is found" % (os_name, command[0]) 

721 ) from err 

722 

723 pid = os.getpid() 

724 if sys.platform == 'sunos5': 

725 lines = call(['ps', '-p', '%d' % pid, '-o', 'osz'], 'Sun OS') 

726 mem = int(lines[-1].strip()) 

727 elif sys.platform == 'linux': 

728 lines = call(['ps', '-p', '%d' % pid, '-o', 'rss,sz'], 'Linux') 

729 mem = int(lines[1].split()[1]) 

730 elif sys.platform == 'darwin': 

731 lines = call(['ps', '-p', '%d' % pid, '-o', 'rss,vsz'], 'Mac OS') 

732 mem = int(lines[1].split()[0]) 

733 elif sys.platform == 'win32': 

734 lines = call(["tasklist", "/nh", "/fi", "pid eq %d" % pid], 'Windows') 

735 mem = int(lines.strip().split()[-2].replace(',', '')) 

736 else: 

737 raise NotImplementedError( 

738 "We don't have a memory monitor for %s" % sys.platform) 

739 return mem 

740 

741 

742def safe_masked_invalid(x, copy=False): 

743 x = np.array(x, subok=True, copy=copy) 

744 if not x.dtype.isnative: 

745 # If we have already made a copy, do the byteswap in place, else make a 

746 # copy with the byte order swapped. 

747 x = x.byteswap(inplace=copy).newbyteorder('N') # Swap to native order. 

748 try: 

749 xm = np.ma.masked_invalid(x, copy=False) 

750 xm.shrink_mask() 

751 except TypeError: 

752 return x 

753 return xm 

754 

755 

756def print_cycles(objects, outstream=sys.stdout, show_progress=False): 

757 """ 

758 Print loops of cyclic references in the given *objects*. 

759 

760 It is often useful to pass in ``gc.garbage`` to find the cycles that are 

761 preventing some objects from being garbage collected. 

762 

763 Parameters 

764 ---------- 

765 objects 

766 A list of objects to find cycles in. 

767 outstream 

768 The stream for output. 

769 show_progress : bool 

770 If True, print the number of objects reached as they are found. 

771 """ 

772 import gc 

773 

774 def print_path(path): 

775 for i, step in enumerate(path): 

776 # next "wraps around" 

777 next = path[(i + 1) % len(path)] 

778 

779 outstream.write(" %s -- " % type(step)) 

780 if isinstance(step, dict): 

781 for key, val in step.items(): 

782 if val is next: 

783 outstream.write("[{!r}]".format(key)) 

784 break 

785 if key is next: 

786 outstream.write("[key] = {!r}".format(val)) 

787 break 

788 elif isinstance(step, list): 

789 outstream.write("[%d]" % step.index(next)) 

790 elif isinstance(step, tuple): 

791 outstream.write("( tuple )") 

792 else: 

793 outstream.write(repr(step)) 

794 outstream.write(" ->\n") 

795 outstream.write("\n") 

796 

797 def recurse(obj, start, all, current_path): 

798 if show_progress: 

799 outstream.write("%d\r" % len(all)) 

800 

801 all[id(obj)] = None 

802 

803 referents = gc.get_referents(obj) 

804 for referent in referents: 

805 # If we've found our way back to the start, this is 

806 # a cycle, so print it out 

807 if referent is start: 

808 print_path(current_path) 

809 

810 # Don't go back through the original list of objects, or 

811 # through temporary references to the object, since those 

812 # are just an artifact of the cycle detector itself. 

813 elif referent is objects or isinstance(referent, types.FrameType): 

814 continue 

815 

816 # We haven't seen this object before, so recurse 

817 elif id(referent) not in all: 

818 recurse(referent, start, all, current_path + [obj]) 

819 

820 for obj in objects: 

821 outstream.write(f"Examining: {obj!r}\n") 

822 recurse(obj, obj, {}, []) 

823 

824 

825class Grouper: 

826 """ 

827 A disjoint-set data structure. 

828 

829 Objects can be joined using :meth:`join`, tested for connectedness 

830 using :meth:`joined`, and all disjoint sets can be retrieved by 

831 using the object as an iterator. 

832 

833 The objects being joined must be hashable and weak-referenceable. 

834 

835 Examples 

836 -------- 

837 >>> from matplotlib.cbook import Grouper 

838 >>> class Foo: 

839 ... def __init__(self, s): 

840 ... self.s = s 

841 ... def __repr__(self): 

842 ... return self.s 

843 ... 

844 >>> a, b, c, d, e, f = [Foo(x) for x in 'abcdef'] 

845 >>> grp = Grouper() 

846 >>> grp.join(a, b) 

847 >>> grp.join(b, c) 

848 >>> grp.join(d, e) 

849 >>> list(grp) 

850 [[a, b, c], [d, e]] 

851 >>> grp.joined(a, b) 

852 True 

853 >>> grp.joined(a, c) 

854 True 

855 >>> grp.joined(a, d) 

856 False 

857 """ 

858 

859 def __init__(self, init=()): 

860 self._mapping = {weakref.ref(x): [weakref.ref(x)] for x in init} 

861 

862 def __contains__(self, item): 

863 return weakref.ref(item) in self._mapping 

864 

865 def clean(self): 

866 """Clean dead weak references from the dictionary.""" 

867 mapping = self._mapping 

868 to_drop = [key for key in mapping if key() is None] 

869 for key in to_drop: 

870 val = mapping.pop(key) 

871 val.remove(key) 

872 

873 def join(self, a, *args): 

874 """ 

875 Join given arguments into the same set. Accepts one or more arguments. 

876 """ 

877 mapping = self._mapping 

878 set_a = mapping.setdefault(weakref.ref(a), [weakref.ref(a)]) 

879 

880 for arg in args: 

881 set_b = mapping.get(weakref.ref(arg), [weakref.ref(arg)]) 

882 if set_b is not set_a: 

883 if len(set_b) > len(set_a): 

884 set_a, set_b = set_b, set_a 

885 set_a.extend(set_b) 

886 for elem in set_b: 

887 mapping[elem] = set_a 

888 

889 self.clean() 

890 

891 def joined(self, a, b): 

892 """Return whether *a* and *b* are members of the same set.""" 

893 self.clean() 

894 return (self._mapping.get(weakref.ref(a), object()) 

895 is self._mapping.get(weakref.ref(b))) 

896 

897 def remove(self, a): 

898 self.clean() 

899 set_a = self._mapping.pop(weakref.ref(a), None) 

900 if set_a: 

901 set_a.remove(weakref.ref(a)) 

902 

903 def __iter__(self): 

904 """ 

905 Iterate over each of the disjoint sets as a list. 

906 

907 The iterator is invalid if interleaved with calls to join(). 

908 """ 

909 self.clean() 

910 unique_groups = {id(group): group for group in self._mapping.values()} 

911 for group in unique_groups.values(): 

912 yield [x() for x in group] 

913 

914 def get_siblings(self, a): 

915 """Return all of the items joined with *a*, including itself.""" 

916 self.clean() 

917 siblings = self._mapping.get(weakref.ref(a), [weakref.ref(a)]) 

918 return [x() for x in siblings] 

919 

920 

921class GrouperView: 

922 """Immutable view over a `.Grouper`.""" 

923 

924 def __init__(self, grouper): 

925 self._grouper = grouper 

926 

927 class _GrouperMethodForwarder: 

928 def __init__(self, deprecated_kw=None): 

929 self._deprecated_kw = deprecated_kw 

930 

931 def __set_name__(self, owner, name): 

932 wrapped = getattr(Grouper, name) 

933 forwarder = functools.wraps(wrapped)( 

934 lambda self, *args, **kwargs: wrapped( 

935 self._grouper, *args, **kwargs)) 

936 if self._deprecated_kw: 

937 forwarder = _api.deprecated(**self._deprecated_kw)(forwarder) 

938 setattr(owner, name, forwarder) 

939 

940 __contains__ = _GrouperMethodForwarder() 

941 __iter__ = _GrouperMethodForwarder() 

942 joined = _GrouperMethodForwarder() 

943 get_siblings = _GrouperMethodForwarder() 

944 clean = _GrouperMethodForwarder(deprecated_kw=dict(since="3.6")) 

945 join = _GrouperMethodForwarder(deprecated_kw=dict(since="3.6")) 

946 remove = _GrouperMethodForwarder(deprecated_kw=dict(since="3.6")) 

947 

948 

949def simple_linear_interpolation(a, steps): 

950 """ 

951 Resample an array with ``steps - 1`` points between original point pairs. 

952 

953 Along each column of *a*, ``(steps - 1)`` points are introduced between 

954 each original values; the values are linearly interpolated. 

955 

956 Parameters 

957 ---------- 

958 a : array, shape (n, ...) 

959 steps : int 

960 

961 Returns 

962 ------- 

963 array 

964 shape ``((n - 1) * steps + 1, ...)`` 

965 """ 

966 fps = a.reshape((len(a), -1)) 

967 xp = np.arange(len(a)) * steps 

968 x = np.arange((len(a) - 1) * steps + 1) 

969 return (np.column_stack([np.interp(x, xp, fp) for fp in fps.T]) 

970 .reshape((len(x),) + a.shape[1:])) 

971 

972 

973def delete_masked_points(*args): 

974 """ 

975 Find all masked and/or non-finite points in a set of arguments, 

976 and return the arguments with only the unmasked points remaining. 

977 

978 Arguments can be in any of 5 categories: 

979 

980 1) 1-D masked arrays 

981 2) 1-D ndarrays 

982 3) ndarrays with more than one dimension 

983 4) other non-string iterables 

984 5) anything else 

985 

986 The first argument must be in one of the first four categories; 

987 any argument with a length differing from that of the first 

988 argument (and hence anything in category 5) then will be 

989 passed through unchanged. 

990 

991 Masks are obtained from all arguments of the correct length 

992 in categories 1, 2, and 4; a point is bad if masked in a masked 

993 array or if it is a nan or inf. No attempt is made to 

994 extract a mask from categories 2, 3, and 4 if `numpy.isfinite` 

995 does not yield a Boolean array. 

996 

997 All input arguments that are not passed unchanged are returned 

998 as ndarrays after removing the points or rows corresponding to 

999 masks in any of the arguments. 

1000 

1001 A vastly simpler version of this function was originally 

1002 written as a helper for Axes.scatter(). 

1003 

1004 """ 

1005 if not len(args): 

1006 return () 

1007 if is_scalar_or_string(args[0]): 

1008 raise ValueError("First argument must be a sequence") 

1009 nrecs = len(args[0]) 

1010 margs = [] 

1011 seqlist = [False] * len(args) 

1012 for i, x in enumerate(args): 

1013 if not isinstance(x, str) and np.iterable(x) and len(x) == nrecs: 

1014 seqlist[i] = True 

1015 if isinstance(x, np.ma.MaskedArray): 

1016 if x.ndim > 1: 

1017 raise ValueError("Masked arrays must be 1-D") 

1018 else: 

1019 x = np.asarray(x) 

1020 margs.append(x) 

1021 masks = [] # List of masks that are True where good. 

1022 for i, x in enumerate(margs): 

1023 if seqlist[i]: 

1024 if x.ndim > 1: 

1025 continue # Don't try to get nan locations unless 1-D. 

1026 if isinstance(x, np.ma.MaskedArray): 

1027 masks.append(~np.ma.getmaskarray(x)) # invert the mask 

1028 xd = x.data 

1029 else: 

1030 xd = x 

1031 try: 

1032 mask = np.isfinite(xd) 

1033 if isinstance(mask, np.ndarray): 

1034 masks.append(mask) 

1035 except Exception: # Fixme: put in tuple of possible exceptions? 

1036 pass 

1037 if len(masks): 

1038 mask = np.logical_and.reduce(masks) 

1039 igood = mask.nonzero()[0] 

1040 if len(igood) < nrecs: 

1041 for i, x in enumerate(margs): 

1042 if seqlist[i]: 

1043 margs[i] = x[igood] 

1044 for i, x in enumerate(margs): 

1045 if seqlist[i] and isinstance(x, np.ma.MaskedArray): 

1046 margs[i] = x.filled() 

1047 return margs 

1048 

1049 

1050def _combine_masks(*args): 

1051 """ 

1052 Find all masked and/or non-finite points in a set of arguments, 

1053 and return the arguments as masked arrays with a common mask. 

1054 

1055 Arguments can be in any of 5 categories: 

1056 

1057 1) 1-D masked arrays 

1058 2) 1-D ndarrays 

1059 3) ndarrays with more than one dimension 

1060 4) other non-string iterables 

1061 5) anything else 

1062 

1063 The first argument must be in one of the first four categories; 

1064 any argument with a length differing from that of the first 

1065 argument (and hence anything in category 5) then will be 

1066 passed through unchanged. 

1067 

1068 Masks are obtained from all arguments of the correct length 

1069 in categories 1, 2, and 4; a point is bad if masked in a masked 

1070 array or if it is a nan or inf. No attempt is made to 

1071 extract a mask from categories 2 and 4 if `numpy.isfinite` 

1072 does not yield a Boolean array. Category 3 is included to 

1073 support RGB or RGBA ndarrays, which are assumed to have only 

1074 valid values and which are passed through unchanged. 

1075 

1076 All input arguments that are not passed unchanged are returned 

1077 as masked arrays if any masked points are found, otherwise as 

1078 ndarrays. 

1079 

1080 """ 

1081 if not len(args): 

1082 return () 

1083 if is_scalar_or_string(args[0]): 

1084 raise ValueError("First argument must be a sequence") 

1085 nrecs = len(args[0]) 

1086 margs = [] # Output args; some may be modified. 

1087 seqlist = [False] * len(args) # Flags: True if output will be masked. 

1088 masks = [] # List of masks. 

1089 for i, x in enumerate(args): 

1090 if is_scalar_or_string(x) or len(x) != nrecs: 

1091 margs.append(x) # Leave it unmodified. 

1092 else: 

1093 if isinstance(x, np.ma.MaskedArray) and x.ndim > 1: 

1094 raise ValueError("Masked arrays must be 1-D") 

1095 try: 

1096 x = np.asanyarray(x) 

1097 except (np.VisibleDeprecationWarning, ValueError): 

1098 # NumPy 1.19 raises a warning about ragged arrays, but we want 

1099 # to accept basically anything here. 

1100 x = np.asanyarray(x, dtype=object) 

1101 if x.ndim == 1: 

1102 x = safe_masked_invalid(x) 

1103 seqlist[i] = True 

1104 if np.ma.is_masked(x): 

1105 masks.append(np.ma.getmaskarray(x)) 

1106 margs.append(x) # Possibly modified. 

1107 if len(masks): 

1108 mask = np.logical_or.reduce(masks) 

1109 for i, x in enumerate(margs): 

1110 if seqlist[i]: 

1111 margs[i] = np.ma.array(x, mask=mask) 

1112 return margs 

1113 

1114 

1115def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None, 

1116 autorange=False): 

1117 r""" 

1118 Return a list of dictionaries of statistics used to draw a series of box 

1119 and whisker plots using `~.Axes.bxp`. 

1120 

1121 Parameters 

1122 ---------- 

1123 X : array-like 

1124 Data that will be represented in the boxplots. Should have 2 or 

1125 fewer dimensions. 

1126 

1127 whis : float or (float, float), default: 1.5 

1128 The position of the whiskers. 

1129 

1130 If a float, the lower whisker is at the lowest datum above 

1131 ``Q1 - whis*(Q3-Q1)``, and the upper whisker at the highest datum below 

1132 ``Q3 + whis*(Q3-Q1)``, where Q1 and Q3 are the first and third 

1133 quartiles. The default value of ``whis = 1.5`` corresponds to Tukey's 

1134 original definition of boxplots. 

1135 

1136 If a pair of floats, they indicate the percentiles at which to draw the 

1137 whiskers (e.g., (5, 95)). In particular, setting this to (0, 100) 

1138 results in whiskers covering the whole range of the data. 

1139 

1140 In the edge case where ``Q1 == Q3``, *whis* is automatically set to 

1141 (0, 100) (cover the whole range of the data) if *autorange* is True. 

1142 

1143 Beyond the whiskers, data are considered outliers and are plotted as 

1144 individual points. 

1145 

1146 bootstrap : int, optional 

1147 Number of times the confidence intervals around the median 

1148 should be bootstrapped (percentile method). 

1149 

1150 labels : array-like, optional 

1151 Labels for each dataset. Length must be compatible with 

1152 dimensions of *X*. 

1153 

1154 autorange : bool, optional (False) 

1155 When `True` and the data are distributed such that the 25th and 75th 

1156 percentiles are equal, ``whis`` is set to (0, 100) such that the 

1157 whisker ends are at the minimum and maximum of the data. 

1158 

1159 Returns 

1160 ------- 

1161 list of dict 

1162 A list of dictionaries containing the results for each column 

1163 of data. Keys of each dictionary are the following: 

1164 

1165 ======== =================================== 

1166 Key Value Description 

1167 ======== =================================== 

1168 label tick label for the boxplot 

1169 mean arithmetic mean value 

1170 med 50th percentile 

1171 q1 first quartile (25th percentile) 

1172 q3 third quartile (75th percentile) 

1173 iqr interquartile range 

1174 cilo lower notch around the median 

1175 cihi upper notch around the median 

1176 whislo end of the lower whisker 

1177 whishi end of the upper whisker 

1178 fliers outliers 

1179 ======== =================================== 

1180 

1181 Notes 

1182 ----- 

1183 Non-bootstrapping approach to confidence interval uses Gaussian-based 

1184 asymptotic approximation: 

1185 

1186 .. math:: 

1187 

1188 \mathrm{med} \pm 1.57 \times \frac{\mathrm{iqr}}{\sqrt{N}} 

1189 

1190 General approach from: 

1191 McGill, R., Tukey, J.W., and Larsen, W.A. (1978) "Variations of 

1192 Boxplots", The American Statistician, 32:12-16. 

1193 """ 

1194 

1195 def _bootstrap_median(data, N=5000): 

1196 # determine 95% confidence intervals of the median 

1197 M = len(data) 

1198 percentiles = [2.5, 97.5] 

1199 

1200 bs_index = np.random.randint(M, size=(N, M)) 

1201 bsData = data[bs_index] 

1202 estimate = np.median(bsData, axis=1, overwrite_input=True) 

1203 

1204 CI = np.percentile(estimate, percentiles) 

1205 return CI 

1206 

1207 def _compute_conf_interval(data, med, iqr, bootstrap): 

1208 if bootstrap is not None: 

1209 # Do a bootstrap estimate of notch locations. 

1210 # get conf. intervals around median 

1211 CI = _bootstrap_median(data, N=bootstrap) 

1212 notch_min = CI[0] 

1213 notch_max = CI[1] 

1214 else: 

1215 

1216 N = len(data) 

1217 notch_min = med - 1.57 * iqr / np.sqrt(N) 

1218 notch_max = med + 1.57 * iqr / np.sqrt(N) 

1219 

1220 return notch_min, notch_max 

1221 

1222 # output is a list of dicts 

1223 bxpstats = [] 

1224 

1225 # convert X to a list of lists 

1226 X = _reshape_2D(X, "X") 

1227 

1228 ncols = len(X) 

1229 if labels is None: 

1230 labels = itertools.repeat(None) 

1231 elif len(labels) != ncols: 

1232 raise ValueError("Dimensions of labels and X must be compatible") 

1233 

1234 input_whis = whis 

1235 for ii, (x, label) in enumerate(zip(X, labels)): 

1236 

1237 # empty dict 

1238 stats = {} 

1239 if label is not None: 

1240 stats['label'] = label 

1241 

1242 # restore whis to the input values in case it got changed in the loop 

1243 whis = input_whis 

1244 

1245 # note tricksiness, append up here and then mutate below 

1246 bxpstats.append(stats) 

1247 

1248 # if empty, bail 

1249 if len(x) == 0: 

1250 stats['fliers'] = np.array([]) 

1251 stats['mean'] = np.nan 

1252 stats['med'] = np.nan 

1253 stats['q1'] = np.nan 

1254 stats['q3'] = np.nan 

1255 stats['iqr'] = np.nan 

1256 stats['cilo'] = np.nan 

1257 stats['cihi'] = np.nan 

1258 stats['whislo'] = np.nan 

1259 stats['whishi'] = np.nan 

1260 continue 

1261 

1262 # up-convert to an array, just to be safe 

1263 x = np.asarray(x) 

1264 

1265 # arithmetic mean 

1266 stats['mean'] = np.mean(x) 

1267 

1268 # medians and quartiles 

1269 q1, med, q3 = np.percentile(x, [25, 50, 75]) 

1270 

1271 # interquartile range 

1272 stats['iqr'] = q3 - q1 

1273 if stats['iqr'] == 0 and autorange: 

1274 whis = (0, 100) 

1275 

1276 # conf. interval around median 

1277 stats['cilo'], stats['cihi'] = _compute_conf_interval( 

1278 x, med, stats['iqr'], bootstrap 

1279 ) 

1280 

1281 # lowest/highest non-outliers 

1282 if np.iterable(whis) and not isinstance(whis, str): 

1283 loval, hival = np.percentile(x, whis) 

1284 elif np.isreal(whis): 

1285 loval = q1 - whis * stats['iqr'] 

1286 hival = q3 + whis * stats['iqr'] 

1287 else: 

1288 raise ValueError('whis must be a float or list of percentiles') 

1289 

1290 # get high extreme 

1291 wiskhi = x[x <= hival] 

1292 if len(wiskhi) == 0 or np.max(wiskhi) < q3: 

1293 stats['whishi'] = q3 

1294 else: 

1295 stats['whishi'] = np.max(wiskhi) 

1296 

1297 # get low extreme 

1298 wisklo = x[x >= loval] 

1299 if len(wisklo) == 0 or np.min(wisklo) > q1: 

1300 stats['whislo'] = q1 

1301 else: 

1302 stats['whislo'] = np.min(wisklo) 

1303 

1304 # compute a single array of outliers 

1305 stats['fliers'] = np.concatenate([ 

1306 x[x < stats['whislo']], 

1307 x[x > stats['whishi']], 

1308 ]) 

1309 

1310 # add in the remaining stats 

1311 stats['q1'], stats['med'], stats['q3'] = q1, med, q3 

1312 

1313 return bxpstats 

1314 

1315 

1316#: Maps short codes for line style to their full name used by backends. 

1317ls_mapper = {'-': 'solid', '--': 'dashed', '-.': 'dashdot', ':': 'dotted'} 

1318#: Maps full names for line styles used by backends to their short codes. 

1319ls_mapper_r = {v: k for k, v in ls_mapper.items()} 

1320 

1321 

1322def contiguous_regions(mask): 

1323 """ 

1324 Return a list of (ind0, ind1) such that ``mask[ind0:ind1].all()`` is 

1325 True and we cover all such regions. 

1326 """ 

1327 mask = np.asarray(mask, dtype=bool) 

1328 

1329 if not mask.size: 

1330 return [] 

1331 

1332 # Find the indices of region changes, and correct offset 

1333 idx, = np.nonzero(mask[:-1] != mask[1:]) 

1334 idx += 1 

1335 

1336 # List operations are faster for moderately sized arrays 

1337 idx = idx.tolist() 

1338 

1339 # Add first and/or last index if needed 

1340 if mask[0]: 

1341 idx = [0] + idx 

1342 if mask[-1]: 

1343 idx.append(len(mask)) 

1344 

1345 return list(zip(idx[::2], idx[1::2])) 

1346 

1347 

1348def is_math_text(s): 

1349 """ 

1350 Return whether the string *s* contains math expressions. 

1351 

1352 This is done by checking whether *s* contains an even number of 

1353 non-escaped dollar signs. 

1354 """ 

1355 s = str(s) 

1356 dollar_count = s.count(r'$') - s.count(r'\$') 

1357 even_dollars = (dollar_count > 0 and dollar_count % 2 == 0) 

1358 return even_dollars 

1359 

1360 

1361def _to_unmasked_float_array(x): 

1362 """ 

1363 Convert a sequence to a float array; if input was a masked array, masked 

1364 values are converted to nans. 

1365 """ 

1366 if hasattr(x, 'mask'): 

1367 return np.ma.asarray(x, float).filled(np.nan) 

1368 else: 

1369 return np.asarray(x, float) 

1370 

1371 

1372def _check_1d(x): 

1373 """Convert scalars to 1D arrays; pass-through arrays as is.""" 

1374 # Unpack in case of e.g. Pandas or xarray object 

1375 x = _unpack_to_numpy(x) 

1376 # plot requires `shape` and `ndim`. If passed an 

1377 # object that doesn't provide them, then force to numpy array. 

1378 # Note this will strip unit information. 

1379 if (not hasattr(x, 'shape') or 

1380 not hasattr(x, 'ndim') or 

1381 len(x.shape) < 1): 

1382 return np.atleast_1d(x) 

1383 else: 

1384 return x 

1385 

1386 

1387def _reshape_2D(X, name): 

1388 """ 

1389 Use Fortran ordering to convert ndarrays and lists of iterables to lists of 

1390 1D arrays. 

1391 

1392 Lists of iterables are converted by applying `numpy.asanyarray` to each of 

1393 their elements. 1D ndarrays are returned in a singleton list containing 

1394 them. 2D ndarrays are converted to the list of their *columns*. 

1395 

1396 *name* is used to generate the error message for invalid inputs. 

1397 """ 

1398 

1399 # Unpack in case of e.g. Pandas or xarray object 

1400 X = _unpack_to_numpy(X) 

1401 

1402 # Iterate over columns for ndarrays. 

1403 if isinstance(X, np.ndarray): 

1404 X = X.T 

1405 

1406 if len(X) == 0: 

1407 return [[]] 

1408 elif X.ndim == 1 and np.ndim(X[0]) == 0: 

1409 # 1D array of scalars: directly return it. 

1410 return [X] 

1411 elif X.ndim in [1, 2]: 

1412 # 2D array, or 1D array of iterables: flatten them first. 

1413 return [np.reshape(x, -1) for x in X] 

1414 else: 

1415 raise ValueError(f'{name} must have 2 or fewer dimensions') 

1416 

1417 # Iterate over list of iterables. 

1418 if len(X) == 0: 

1419 return [[]] 

1420 

1421 result = [] 

1422 is_1d = True 

1423 for xi in X: 

1424 # check if this is iterable, except for strings which we 

1425 # treat as singletons. 

1426 if not isinstance(xi, str): 

1427 try: 

1428 iter(xi) 

1429 except TypeError: 

1430 pass 

1431 else: 

1432 is_1d = False 

1433 xi = np.asanyarray(xi) 

1434 nd = np.ndim(xi) 

1435 if nd > 1: 

1436 raise ValueError(f'{name} must have 2 or fewer dimensions') 

1437 result.append(xi.reshape(-1)) 

1438 

1439 if is_1d: 

1440 # 1D array of scalars: directly return it. 

1441 return [np.reshape(result, -1)] 

1442 else: 

1443 # 2D array, or 1D array of iterables: use flattened version. 

1444 return result 

1445 

1446 

1447def violin_stats(X, method, points=100, quantiles=None): 

1448 """ 

1449 Return a list of dictionaries of data which can be used to draw a series 

1450 of violin plots. 

1451 

1452 See the ``Returns`` section below to view the required keys of the 

1453 dictionary. 

1454 

1455 Users can skip this function and pass a user-defined set of dictionaries 

1456 with the same keys to `~.axes.Axes.violinplot` instead of using Matplotlib 

1457 to do the calculations. See the *Returns* section below for the keys 

1458 that must be present in the dictionaries. 

1459 

1460 Parameters 

1461 ---------- 

1462 X : array-like 

1463 Sample data that will be used to produce the gaussian kernel density 

1464 estimates. Must have 2 or fewer dimensions. 

1465 

1466 method : callable 

1467 The method used to calculate the kernel density estimate for each 

1468 column of data. When called via ``method(v, coords)``, it should 

1469 return a vector of the values of the KDE evaluated at the values 

1470 specified in coords. 

1471 

1472 points : int, default: 100 

1473 Defines the number of points to evaluate each of the gaussian kernel 

1474 density estimates at. 

1475 

1476 quantiles : array-like, default: None 

1477 Defines (if not None) a list of floats in interval [0, 1] for each 

1478 column of data, which represents the quantiles that will be rendered 

1479 for that column of data. Must have 2 or fewer dimensions. 1D array will 

1480 be treated as a singleton list containing them. 

1481 

1482 Returns 

1483 ------- 

1484 list of dict 

1485 A list of dictionaries containing the results for each column of data. 

1486 The dictionaries contain at least the following: 

1487 

1488 - coords: A list of scalars containing the coordinates this particular 

1489 kernel density estimate was evaluated at. 

1490 - vals: A list of scalars containing the values of the kernel density 

1491 estimate at each of the coordinates given in *coords*. 

1492 - mean: The mean value for this column of data. 

1493 - median: The median value for this column of data. 

1494 - min: The minimum value for this column of data. 

1495 - max: The maximum value for this column of data. 

1496 - quantiles: The quantile values for this column of data. 

1497 """ 

1498 

1499 # List of dictionaries describing each of the violins. 

1500 vpstats = [] 

1501 

1502 # Want X to be a list of data sequences 

1503 X = _reshape_2D(X, "X") 

1504 

1505 # Want quantiles to be as the same shape as data sequences 

1506 if quantiles is not None and len(quantiles) != 0: 

1507 quantiles = _reshape_2D(quantiles, "quantiles") 

1508 # Else, mock quantiles if it's none or empty 

1509 else: 

1510 quantiles = [[]] * len(X) 

1511 

1512 # quantiles should has the same size as dataset 

1513 if len(X) != len(quantiles): 

1514 raise ValueError("List of violinplot statistics and quantiles values" 

1515 " must have the same length") 

1516 

1517 # Zip x and quantiles 

1518 for (x, q) in zip(X, quantiles): 

1519 # Dictionary of results for this distribution 

1520 stats = {} 

1521 

1522 # Calculate basic stats for the distribution 

1523 min_val = np.min(x) 

1524 max_val = np.max(x) 

1525 quantile_val = np.percentile(x, 100 * q) 

1526 

1527 # Evaluate the kernel density estimate 

1528 coords = np.linspace(min_val, max_val, points) 

1529 stats['vals'] = method(x, coords) 

1530 stats['coords'] = coords 

1531 

1532 # Store additional statistics for this distribution 

1533 stats['mean'] = np.mean(x) 

1534 stats['median'] = np.median(x) 

1535 stats['min'] = min_val 

1536 stats['max'] = max_val 

1537 stats['quantiles'] = np.atleast_1d(quantile_val) 

1538 

1539 # Append to output 

1540 vpstats.append(stats) 

1541 

1542 return vpstats 

1543 

1544 

1545def pts_to_prestep(x, *args): 

1546 """ 

1547 Convert continuous line to pre-steps. 

1548 

1549 Given a set of ``N`` points, convert to ``2N - 1`` points, which when 

1550 connected linearly give a step function which changes values at the 

1551 beginning of the intervals. 

1552 

1553 Parameters 

1554 ---------- 

1555 x : array 

1556 The x location of the steps. May be empty. 

1557 

1558 y1, ..., yp : array 

1559 y arrays to be turned into steps; all must be the same length as ``x``. 

1560 

1561 Returns 

1562 ------- 

1563 array 

1564 The x and y values converted to steps in the same order as the input; 

1565 can be unpacked as ``x_out, y1_out, ..., yp_out``. If the input is 

1566 length ``N``, each of these arrays will be length ``2N + 1``. For 

1567 ``N=0``, the length will be 0. 

1568 

1569 Examples 

1570 -------- 

1571 >>> x_s, y1_s, y2_s = pts_to_prestep(x, y1, y2) 

1572 """ 

1573 steps = np.zeros((1 + len(args), max(2 * len(x) - 1, 0))) 

1574 # In all `pts_to_*step` functions, only assign once using *x* and *args*, 

1575 # as converting to an array may be expensive. 

1576 steps[0, 0::2] = x 

1577 steps[0, 1::2] = steps[0, 0:-2:2] 

1578 steps[1:, 0::2] = args 

1579 steps[1:, 1::2] = steps[1:, 2::2] 

1580 return steps 

1581 

1582 

1583def pts_to_poststep(x, *args): 

1584 """ 

1585 Convert continuous line to post-steps. 

1586 

1587 Given a set of ``N`` points convert to ``2N + 1`` points, which when 

1588 connected linearly give a step function which changes values at the end of 

1589 the intervals. 

1590 

1591 Parameters 

1592 ---------- 

1593 x : array 

1594 The x location of the steps. May be empty. 

1595 

1596 y1, ..., yp : array 

1597 y arrays to be turned into steps; all must be the same length as ``x``. 

1598 

1599 Returns 

1600 ------- 

1601 array 

1602 The x and y values converted to steps in the same order as the input; 

1603 can be unpacked as ``x_out, y1_out, ..., yp_out``. If the input is 

1604 length ``N``, each of these arrays will be length ``2N + 1``. For 

1605 ``N=0``, the length will be 0. 

1606 

1607 Examples 

1608 -------- 

1609 >>> x_s, y1_s, y2_s = pts_to_poststep(x, y1, y2) 

1610 """ 

1611 steps = np.zeros((1 + len(args), max(2 * len(x) - 1, 0))) 

1612 steps[0, 0::2] = x 

1613 steps[0, 1::2] = steps[0, 2::2] 

1614 steps[1:, 0::2] = args 

1615 steps[1:, 1::2] = steps[1:, 0:-2:2] 

1616 return steps 

1617 

1618 

1619def pts_to_midstep(x, *args): 

1620 """ 

1621 Convert continuous line to mid-steps. 

1622 

1623 Given a set of ``N`` points convert to ``2N`` points which when connected 

1624 linearly give a step function which changes values at the middle of the 

1625 intervals. 

1626 

1627 Parameters 

1628 ---------- 

1629 x : array 

1630 The x location of the steps. May be empty. 

1631 

1632 y1, ..., yp : array 

1633 y arrays to be turned into steps; all must be the same length as 

1634 ``x``. 

1635 

1636 Returns 

1637 ------- 

1638 array 

1639 The x and y values converted to steps in the same order as the input; 

1640 can be unpacked as ``x_out, y1_out, ..., yp_out``. If the input is 

1641 length ``N``, each of these arrays will be length ``2N``. 

1642 

1643 Examples 

1644 -------- 

1645 >>> x_s, y1_s, y2_s = pts_to_midstep(x, y1, y2) 

1646 """ 

1647 steps = np.zeros((1 + len(args), 2 * len(x))) 

1648 x = np.asanyarray(x) 

1649 steps[0, 1:-1:2] = steps[0, 2::2] = (x[:-1] + x[1:]) / 2 

1650 steps[0, :1] = x[:1] # Also works for zero-sized input. 

1651 steps[0, -1:] = x[-1:] 

1652 steps[1:, 0::2] = args 

1653 steps[1:, 1::2] = steps[1:, 0::2] 

1654 return steps 

1655 

1656 

1657STEP_LOOKUP_MAP = {'default': lambda x, y: (x, y), 

1658 'steps': pts_to_prestep, 

1659 'steps-pre': pts_to_prestep, 

1660 'steps-post': pts_to_poststep, 

1661 'steps-mid': pts_to_midstep} 

1662 

1663 

1664def index_of(y): 

1665 """ 

1666 A helper function to create reasonable x values for the given *y*. 

1667 

1668 This is used for plotting (x, y) if x values are not explicitly given. 

1669 

1670 First try ``y.index`` (assuming *y* is a `pandas.Series`), if that 

1671 fails, use ``range(len(y))``. 

1672 

1673 This will be extended in the future to deal with more types of 

1674 labeled data. 

1675 

1676 Parameters 

1677 ---------- 

1678 y : float or array-like 

1679 

1680 Returns 

1681 ------- 

1682 x, y : ndarray 

1683 The x and y values to plot. 

1684 """ 

1685 try: 

1686 return y.index.to_numpy(), y.to_numpy() 

1687 except AttributeError: 

1688 pass 

1689 try: 

1690 y = _check_1d(y) 

1691 except (np.VisibleDeprecationWarning, ValueError): 

1692 # NumPy 1.19 will warn on ragged input, and we can't actually use it. 

1693 pass 

1694 else: 

1695 return np.arange(y.shape[0], dtype=float), y 

1696 raise ValueError('Input could not be cast to an at-least-1D NumPy array') 

1697 

1698 

1699def safe_first_element(obj): 

1700 """ 

1701 Return the first element in *obj*. 

1702 

1703 This is an type-independent way of obtaining the first element, 

1704 supporting both index access and the iterator protocol. 

1705 """ 

1706 return _safe_first_finite(obj, skip_nonfinite=False) 

1707 

1708 

1709def _safe_first_finite(obj, *, skip_nonfinite=True): 

1710 """ 

1711 Return the first non-None (and optionally finite) element in *obj*. 

1712 

1713 This is a method for internal use. 

1714 

1715 This is an type-independent way of obtaining the first non-None element, 

1716 supporting both index access and the iterator protocol. 

1717 The first non-None element will be obtained when skip_none is True. 

1718 """ 

1719 def safe_isfinite(val): 

1720 if val is None: 

1721 return False 

1722 try: 

1723 return np.isfinite(val) if np.isscalar(val) else True 

1724 except TypeError: 

1725 # This is something that numpy can not make heads or tails 

1726 # of, assume "finite" 

1727 return True 

1728 if skip_nonfinite is False: 

1729 if isinstance(obj, collections.abc.Iterator): 

1730 # needed to accept `array.flat` as input. 

1731 # np.flatiter reports as an instance of collections.Iterator 

1732 # but can still be indexed via []. 

1733 # This has the side effect of re-setting the iterator, but 

1734 # that is acceptable. 

1735 try: 

1736 return obj[0] 

1737 except TypeError: 

1738 pass 

1739 raise RuntimeError("matplotlib does not support generators " 

1740 "as input") 

1741 return next(iter(obj)) 

1742 elif isinstance(obj, np.flatiter): 

1743 # TODO do the finite filtering on this 

1744 return obj[0] 

1745 elif isinstance(obj, collections.abc.Iterator): 

1746 raise RuntimeError("matplotlib does not " 

1747 "support generators as input") 

1748 else: 

1749 return next(val for val in obj if safe_isfinite(val)) 

1750 

1751 

1752def sanitize_sequence(data): 

1753 """ 

1754 Convert dictview objects to list. Other inputs are returned unchanged. 

1755 """ 

1756 return (list(data) if isinstance(data, collections.abc.MappingView) 

1757 else data) 

1758 

1759 

1760def normalize_kwargs(kw, alias_mapping=None): 

1761 """ 

1762 Helper function to normalize kwarg inputs. 

1763 

1764 Parameters 

1765 ---------- 

1766 kw : dict or None 

1767 A dict of keyword arguments. None is explicitly supported and treated 

1768 as an empty dict, to support functions with an optional parameter of 

1769 the form ``props=None``. 

1770 

1771 alias_mapping : dict or Artist subclass or Artist instance, optional 

1772 A mapping between a canonical name to a list of aliases, in order of 

1773 precedence from lowest to highest. 

1774 

1775 If the canonical value is not in the list it is assumed to have the 

1776 highest priority. 

1777 

1778 If an Artist subclass or instance is passed, use its properties alias 

1779 mapping. 

1780 

1781 Raises 

1782 ------ 

1783 TypeError 

1784 To match what Python raises if invalid arguments/keyword arguments are 

1785 passed to a callable. 

1786 """ 

1787 from matplotlib.artist import Artist 

1788 

1789 if kw is None: 

1790 return {} 

1791 

1792 # deal with default value of alias_mapping 

1793 if alias_mapping is None: 

1794 alias_mapping = dict() 

1795 elif (isinstance(alias_mapping, type) and issubclass(alias_mapping, Artist) 

1796 or isinstance(alias_mapping, Artist)): 

1797 alias_mapping = getattr(alias_mapping, "_alias_map", {}) 

1798 

1799 to_canonical = {alias: canonical 

1800 for canonical, alias_list in alias_mapping.items() 

1801 for alias in alias_list} 

1802 canonical_to_seen = {} 

1803 ret = {} # output dictionary 

1804 

1805 for k, v in kw.items(): 

1806 canonical = to_canonical.get(k, k) 

1807 if canonical in canonical_to_seen: 

1808 raise TypeError(f"Got both {canonical_to_seen[canonical]!r} and " 

1809 f"{k!r}, which are aliases of one another") 

1810 canonical_to_seen[canonical] = k 

1811 ret[canonical] = v 

1812 

1813 return ret 

1814 

1815 

1816@contextlib.contextmanager 

1817def _lock_path(path): 

1818 """ 

1819 Context manager for locking a path. 

1820 

1821 Usage:: 

1822 

1823 with _lock_path(path): 

1824 ... 

1825 

1826 Another thread or process that attempts to lock the same path will wait 

1827 until this context manager is exited. 

1828 

1829 The lock is implemented by creating a temporary file in the parent 

1830 directory, so that directory must exist and be writable. 

1831 """ 

1832 path = Path(path) 

1833 lock_path = path.with_name(path.name + ".matplotlib-lock") 

1834 retries = 50 

1835 sleeptime = 0.1 

1836 for _ in range(retries): 

1837 try: 

1838 with lock_path.open("xb"): 

1839 break 

1840 except FileExistsError: 

1841 time.sleep(sleeptime) 

1842 else: 

1843 raise TimeoutError("""\ 

1844Lock error: Matplotlib failed to acquire the following lock file: 

1845 {} 

1846This maybe due to another process holding this lock file. If you are sure no 

1847other Matplotlib process is running, remove this file and try again.""".format( 

1848 lock_path)) 

1849 try: 

1850 yield 

1851 finally: 

1852 lock_path.unlink() 

1853 

1854 

1855def _topmost_artist( 

1856 artists, 

1857 _cached_max=functools.partial(max, key=operator.attrgetter("zorder"))): 

1858 """ 

1859 Get the topmost artist of a list. 

1860 

1861 In case of a tie, return the *last* of the tied artists, as it will be 

1862 drawn on top of the others. `max` returns the first maximum in case of 

1863 ties, so we need to iterate over the list in reverse order. 

1864 """ 

1865 return _cached_max(reversed(artists)) 

1866 

1867 

1868def _str_equal(obj, s): 

1869 """ 

1870 Return whether *obj* is a string equal to string *s*. 

1871 

1872 This helper solely exists to handle the case where *obj* is a numpy array, 

1873 because in such cases, a naive ``obj == s`` would yield an array, which 

1874 cannot be used in a boolean context. 

1875 """ 

1876 return isinstance(obj, str) and obj == s 

1877 

1878 

1879def _str_lower_equal(obj, s): 

1880 """ 

1881 Return whether *obj* is a string equal, when lowercased, to string *s*. 

1882 

1883 This helper solely exists to handle the case where *obj* is a numpy array, 

1884 because in such cases, a naive ``obj == s`` would yield an array, which 

1885 cannot be used in a boolean context. 

1886 """ 

1887 return isinstance(obj, str) and obj.lower() == s 

1888 

1889 

1890def _array_perimeter(arr): 

1891 """ 

1892 Get the elements on the perimeter of *arr*. 

1893 

1894 Parameters 

1895 ---------- 

1896 arr : ndarray, shape (M, N) 

1897 The input array. 

1898 

1899 Returns 

1900 ------- 

1901 ndarray, shape (2*(M - 1) + 2*(N - 1),) 

1902 The elements on the perimeter of the array:: 

1903 

1904 [arr[0, 0], ..., arr[0, -1], ..., arr[-1, -1], ..., arr[-1, 0], ...] 

1905 

1906 Examples 

1907 -------- 

1908 >>> i, j = np.ogrid[:3, :4] 

1909 >>> a = i*10 + j 

1910 >>> a 

1911 array([[ 0, 1, 2, 3], 

1912 [10, 11, 12, 13], 

1913 [20, 21, 22, 23]]) 

1914 >>> _array_perimeter(a) 

1915 array([ 0, 1, 2, 3, 13, 23, 22, 21, 20, 10]) 

1916 """ 

1917 # note we use Python's half-open ranges to avoid repeating 

1918 # the corners 

1919 forward = np.s_[0:-1] # [0 ... -1) 

1920 backward = np.s_[-1:0:-1] # [-1 ... 0) 

1921 return np.concatenate(( 

1922 arr[0, forward], 

1923 arr[forward, -1], 

1924 arr[-1, backward], 

1925 arr[backward, 0], 

1926 )) 

1927 

1928 

1929def _unfold(arr, axis, size, step): 

1930 """ 

1931 Append an extra dimension containing sliding windows along *axis*. 

1932 

1933 All windows are of size *size* and begin with every *step* elements. 

1934 

1935 Parameters 

1936 ---------- 

1937 arr : ndarray, shape (N_1, ..., N_k) 

1938 The input array 

1939 axis : int 

1940 Axis along which the windows are extracted 

1941 size : int 

1942 Size of the windows 

1943 step : int 

1944 Stride between first elements of subsequent windows. 

1945 

1946 Returns 

1947 ------- 

1948 ndarray, shape (N_1, ..., 1 + (N_axis-size)/step, ..., N_k, size) 

1949 

1950 Examples 

1951 -------- 

1952 >>> i, j = np.ogrid[:3, :7] 

1953 >>> a = i*10 + j 

1954 >>> a 

1955 array([[ 0, 1, 2, 3, 4, 5, 6], 

1956 [10, 11, 12, 13, 14, 15, 16], 

1957 [20, 21, 22, 23, 24, 25, 26]]) 

1958 >>> _unfold(a, axis=1, size=3, step=2) 

1959 array([[[ 0, 1, 2], 

1960 [ 2, 3, 4], 

1961 [ 4, 5, 6]], 

1962 [[10, 11, 12], 

1963 [12, 13, 14], 

1964 [14, 15, 16]], 

1965 [[20, 21, 22], 

1966 [22, 23, 24], 

1967 [24, 25, 26]]]) 

1968 """ 

1969 new_shape = [*arr.shape, size] 

1970 new_strides = [*arr.strides, arr.strides[axis]] 

1971 new_shape[axis] = (new_shape[axis] - size) // step + 1 

1972 new_strides[axis] = new_strides[axis] * step 

1973 return np.lib.stride_tricks.as_strided(arr, 

1974 shape=new_shape, 

1975 strides=new_strides, 

1976 writeable=False) 

1977 

1978 

1979def _array_patch_perimeters(x, rstride, cstride): 

1980 """ 

1981 Extract perimeters of patches from *arr*. 

1982 

1983 Extracted patches are of size (*rstride* + 1) x (*cstride* + 1) and 

1984 share perimeters with their neighbors. The ordering of the vertices matches 

1985 that returned by ``_array_perimeter``. 

1986 

1987 Parameters 

1988 ---------- 

1989 x : ndarray, shape (N, M) 

1990 Input array 

1991 rstride : int 

1992 Vertical (row) stride between corresponding elements of each patch 

1993 cstride : int 

1994 Horizontal (column) stride between corresponding elements of each patch 

1995 

1996 Returns 

1997 ------- 

1998 ndarray, shape (N/rstride * M/cstride, 2 * (rstride + cstride)) 

1999 """ 

2000 assert rstride > 0 and cstride > 0 

2001 assert (x.shape[0] - 1) % rstride == 0 

2002 assert (x.shape[1] - 1) % cstride == 0 

2003 # We build up each perimeter from four half-open intervals. Here is an 

2004 # illustrated explanation for rstride == cstride == 3 

2005 # 

2006 # T T T R 

2007 # L R 

2008 # L R 

2009 # L B B B 

2010 # 

2011 # where T means that this element will be in the top array, R for right, 

2012 # B for bottom and L for left. Each of the arrays below has a shape of: 

2013 # 

2014 # (number of perimeters that can be extracted vertically, 

2015 # number of perimeters that can be extracted horizontally, 

2016 # cstride for top and bottom and rstride for left and right) 

2017 # 

2018 # Note that _unfold doesn't incur any memory copies, so the only costly 

2019 # operation here is the np.concatenate. 

2020 top = _unfold(x[:-1:rstride, :-1], 1, cstride, cstride) 

2021 bottom = _unfold(x[rstride::rstride, 1:], 1, cstride, cstride)[..., ::-1] 

2022 right = _unfold(x[:-1, cstride::cstride], 0, rstride, rstride) 

2023 left = _unfold(x[1:, :-1:cstride], 0, rstride, rstride)[..., ::-1] 

2024 return (np.concatenate((top, right, bottom, left), axis=2) 

2025 .reshape(-1, 2 * (rstride + cstride))) 

2026 

2027 

2028@contextlib.contextmanager 

2029def _setattr_cm(obj, **kwargs): 

2030 """ 

2031 Temporarily set some attributes; restore original state at context exit. 

2032 """ 

2033 sentinel = object() 

2034 origs = {} 

2035 for attr in kwargs: 

2036 orig = getattr(obj, attr, sentinel) 

2037 if attr in obj.__dict__ or orig is sentinel: 

2038 # if we are pulling from the instance dict or the object 

2039 # does not have this attribute we can trust the above 

2040 origs[attr] = orig 

2041 else: 

2042 # if the attribute is not in the instance dict it must be 

2043 # from the class level 

2044 cls_orig = getattr(type(obj), attr) 

2045 # if we are dealing with a property (but not a general descriptor) 

2046 # we want to set the original value back. 

2047 if isinstance(cls_orig, property): 

2048 origs[attr] = orig 

2049 # otherwise this is _something_ we are going to shadow at 

2050 # the instance dict level from higher up in the MRO. We 

2051 # are going to assume we can delattr(obj, attr) to clean 

2052 # up after ourselves. It is possible that this code will 

2053 # fail if used with a non-property custom descriptor which 

2054 # implements __set__ (and __delete__ does not act like a 

2055 # stack). However, this is an internal tool and we do not 

2056 # currently have any custom descriptors. 

2057 else: 

2058 origs[attr] = sentinel 

2059 

2060 try: 

2061 for attr, val in kwargs.items(): 

2062 setattr(obj, attr, val) 

2063 yield 

2064 finally: 

2065 for attr, orig in origs.items(): 

2066 if orig is sentinel: 

2067 delattr(obj, attr) 

2068 else: 

2069 setattr(obj, attr, orig) 

2070 

2071 

2072class _OrderedSet(collections.abc.MutableSet): 

2073 def __init__(self): 

2074 self._od = collections.OrderedDict() 

2075 

2076 def __contains__(self, key): 

2077 return key in self._od 

2078 

2079 def __iter__(self): 

2080 return iter(self._od) 

2081 

2082 def __len__(self): 

2083 return len(self._od) 

2084 

2085 def add(self, key): 

2086 self._od.pop(key, None) 

2087 self._od[key] = None 

2088 

2089 def discard(self, key): 

2090 self._od.pop(key, None) 

2091 

2092 

2093# Agg's buffers are unmultiplied RGBA8888, which neither PyQt<=5.1 nor cairo 

2094# support; however, both do support premultiplied ARGB32. 

2095 

2096 

2097def _premultiplied_argb32_to_unmultiplied_rgba8888(buf): 

2098 """ 

2099 Convert a premultiplied ARGB32 buffer to an unmultiplied RGBA8888 buffer. 

2100 """ 

2101 rgba = np.take( # .take() ensures C-contiguity of the result. 

2102 buf, 

2103 [2, 1, 0, 3] if sys.byteorder == "little" else [1, 2, 3, 0], axis=2) 

2104 rgb = rgba[..., :-1] 

2105 alpha = rgba[..., -1] 

2106 # Un-premultiply alpha. The formula is the same as in cairo-png.c. 

2107 mask = alpha != 0 

2108 for channel in np.rollaxis(rgb, -1): 

2109 channel[mask] = ( 

2110 (channel[mask].astype(int) * 255 + alpha[mask] // 2) 

2111 // alpha[mask]) 

2112 return rgba 

2113 

2114 

2115def _unmultiplied_rgba8888_to_premultiplied_argb32(rgba8888): 

2116 """ 

2117 Convert an unmultiplied RGBA8888 buffer to a premultiplied ARGB32 buffer. 

2118 """ 

2119 if sys.byteorder == "little": 

2120 argb32 = np.take(rgba8888, [2, 1, 0, 3], axis=2) 

2121 rgb24 = argb32[..., :-1] 

2122 alpha8 = argb32[..., -1:] 

2123 else: 

2124 argb32 = np.take(rgba8888, [3, 0, 1, 2], axis=2) 

2125 alpha8 = argb32[..., :1] 

2126 rgb24 = argb32[..., 1:] 

2127 # Only bother premultiplying when the alpha channel is not fully opaque, 

2128 # as the cost is not negligible. The unsafe cast is needed to do the 

2129 # multiplication in-place in an integer buffer. 

2130 if alpha8.min() != 0xff: 

2131 np.multiply(rgb24, alpha8 / 0xff, out=rgb24, casting="unsafe") 

2132 return argb32 

2133 

2134 

2135def _get_nonzero_slices(buf): 

2136 """ 

2137 Return the bounds of the nonzero region of a 2D array as a pair of slices. 

2138 

2139 ``buf[_get_nonzero_slices(buf)]`` is the smallest sub-rectangle in *buf* 

2140 that encloses all non-zero entries in *buf*. If *buf* is fully zero, then 

2141 ``(slice(0, 0), slice(0, 0))`` is returned. 

2142 """ 

2143 x_nz, = buf.any(axis=0).nonzero() 

2144 y_nz, = buf.any(axis=1).nonzero() 

2145 if len(x_nz) and len(y_nz): 

2146 l, r = x_nz[[0, -1]] 

2147 b, t = y_nz[[0, -1]] 

2148 return slice(b, t + 1), slice(l, r + 1) 

2149 else: 

2150 return slice(0, 0), slice(0, 0) 

2151 

2152 

2153def _pformat_subprocess(command): 

2154 """Pretty-format a subprocess command for printing/logging purposes.""" 

2155 return (command if isinstance(command, str) 

2156 else " ".join(shlex.quote(os.fspath(arg)) for arg in command)) 

2157 

2158 

2159def _check_and_log_subprocess(command, logger, **kwargs): 

2160 """ 

2161 Run *command*, returning its stdout output if it succeeds. 

2162 

2163 If it fails (exits with nonzero return code), raise an exception whose text 

2164 includes the failed command and captured stdout and stderr output. 

2165 

2166 Regardless of the return code, the command is logged at DEBUG level on 

2167 *logger*. In case of success, the output is likewise logged. 

2168 """ 

2169 logger.debug('%s', _pformat_subprocess(command)) 

2170 proc = subprocess.run( 

2171 command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) 

2172 if proc.returncode: 

2173 stdout = proc.stdout 

2174 if isinstance(stdout, bytes): 

2175 stdout = stdout.decode() 

2176 stderr = proc.stderr 

2177 if isinstance(stderr, bytes): 

2178 stderr = stderr.decode() 

2179 raise RuntimeError( 

2180 f"The command\n" 

2181 f" {_pformat_subprocess(command)}\n" 

2182 f"failed and generated the following output:\n" 

2183 f"{stdout}\n" 

2184 f"and the following error:\n" 

2185 f"{stderr}") 

2186 if proc.stdout: 

2187 logger.debug("stdout:\n%s", proc.stdout) 

2188 if proc.stderr: 

2189 logger.debug("stderr:\n%s", proc.stderr) 

2190 return proc.stdout 

2191 

2192 

2193def _backend_module_name(name): 

2194 """ 

2195 Convert a backend name (either a standard backend -- "Agg", "TkAgg", ... -- 

2196 or a custom backend -- "module://...") to the corresponding module name). 

2197 """ 

2198 return (name[9:] if name.startswith("module://") 

2199 else "matplotlib.backends.backend_{}".format(name.lower())) 

2200 

2201 

2202def _setup_new_guiapp(): 

2203 """ 

2204 Perform OS-dependent setup when Matplotlib creates a new GUI application. 

2205 """ 

2206 # Windows: If not explicit app user model id has been set yet (so we're not 

2207 # already embedded), then set it to "matplotlib", so that taskbar icons are 

2208 # correct. 

2209 try: 

2210 _c_internal_utils.Win32_GetCurrentProcessExplicitAppUserModelID() 

2211 except OSError: 

2212 _c_internal_utils.Win32_SetCurrentProcessExplicitAppUserModelID( 

2213 "matplotlib") 

2214 

2215 

2216def _format_approx(number, precision): 

2217 """ 

2218 Format the number with at most the number of decimals given as precision. 

2219 Remove trailing zeros and possibly the decimal point. 

2220 """ 

2221 return f'{number:.{precision}f}'.rstrip('0').rstrip('.') or '0' 

2222 

2223 

2224def _g_sig_digits(value, delta): 

2225 """ 

2226 Return the number of significant digits to %g-format *value*, assuming that 

2227 it is known with an error of *delta*. 

2228 """ 

2229 if delta == 0: 

2230 # delta = 0 may occur when trying to format values over a tiny range; 

2231 # in that case, replace it by the distance to the closest float. 

2232 delta = abs(np.spacing(value)) 

2233 # If e.g. value = 45.67 and delta = 0.02, then we want to round to 2 digits 

2234 # after the decimal point (floor(log10(0.02)) = -2); 45.67 contributes 2 

2235 # digits before the decimal point (floor(log10(45.67)) + 1 = 2): the total 

2236 # is 4 significant digits. A value of 0 contributes 1 "digit" before the 

2237 # decimal point. 

2238 # For inf or nan, the precision doesn't matter. 

2239 return max( 

2240 0, 

2241 (math.floor(math.log10(abs(value))) + 1 if value else 1) 

2242 - math.floor(math.log10(delta))) if math.isfinite(value) else 0 

2243 

2244 

2245def _unikey_or_keysym_to_mplkey(unikey, keysym): 

2246 """ 

2247 Convert a Unicode key or X keysym to a Matplotlib key name. 

2248 

2249 The Unicode key is checked first; this avoids having to list most printable 

2250 keysyms such as ``EuroSign``. 

2251 """ 

2252 # For non-printable characters, gtk3 passes "\0" whereas tk passes an "". 

2253 if unikey and unikey.isprintable(): 

2254 return unikey 

2255 key = keysym.lower() 

2256 if key.startswith("kp_"): # keypad_x (including kp_enter). 

2257 key = key[3:] 

2258 if key.startswith("page_"): # page_{up,down} 

2259 key = key.replace("page_", "page") 

2260 if key.endswith(("_l", "_r")): # alt_l, ctrl_l, shift_l. 

2261 key = key[:-2] 

2262 key = { 

2263 "return": "enter", 

2264 "prior": "pageup", # Used by tk. 

2265 "next": "pagedown", # Used by tk. 

2266 }.get(key, key) 

2267 return key 

2268 

2269 

2270@functools.lru_cache(None) 

2271def _make_class_factory(mixin_class, fmt, attr_name=None): 

2272 """ 

2273 Return a function that creates picklable classes inheriting from a mixin. 

2274 

2275 After :: 

2276 

2277 factory = _make_class_factory(FooMixin, fmt, attr_name) 

2278 FooAxes = factory(Axes) 

2279 

2280 ``Foo`` is a class that inherits from ``FooMixin`` and ``Axes`` and **is 

2281 picklable** (picklability is what differentiates this from a plain call to 

2282 `type`). Its ``__name__`` is set to ``fmt.format(Axes.__name__)`` and the 

2283 base class is stored in the ``attr_name`` attribute, if not None. 

2284 

2285 Moreover, the return value of ``factory`` is memoized: calls with the same 

2286 ``Axes`` class always return the same subclass. 

2287 """ 

2288 

2289 @functools.lru_cache(None) 

2290 def class_factory(axes_class): 

2291 # if we have already wrapped this class, declare victory! 

2292 if issubclass(axes_class, mixin_class): 

2293 return axes_class 

2294 

2295 # The parameter is named "axes_class" for backcompat but is really just 

2296 # a base class; no axes semantics are used. 

2297 base_class = axes_class 

2298 

2299 class subcls(mixin_class, base_class): 

2300 # Better approximation than __module__ = "matplotlib.cbook". 

2301 __module__ = mixin_class.__module__ 

2302 

2303 def __reduce__(self): 

2304 return (_picklable_class_constructor, 

2305 (mixin_class, fmt, attr_name, base_class), 

2306 self.__getstate__()) 

2307 

2308 subcls.__name__ = subcls.__qualname__ = fmt.format(base_class.__name__) 

2309 if attr_name is not None: 

2310 setattr(subcls, attr_name, base_class) 

2311 return subcls 

2312 

2313 class_factory.__module__ = mixin_class.__module__ 

2314 return class_factory 

2315 

2316 

2317def _picklable_class_constructor(mixin_class, fmt, attr_name, base_class): 

2318 """Internal helper for _make_class_factory.""" 

2319 factory = _make_class_factory(mixin_class, fmt, attr_name) 

2320 cls = factory(base_class) 

2321 return cls.__new__(cls) 

2322 

2323 

2324def _unpack_to_numpy(x): 

2325 """Internal helper to extract data from e.g. pandas and xarray objects.""" 

2326 if isinstance(x, np.ndarray): 

2327 # If numpy, return directly 

2328 return x 

2329 if hasattr(x, 'to_numpy'): 

2330 # Assume that any function to_numpy() do actually return a numpy array 

2331 return x.to_numpy() 

2332 if hasattr(x, 'values'): 

2333 xtmp = x.values 

2334 # For example a dict has a 'values' attribute, but it is not a property 

2335 # so in this case we do not want to return a function 

2336 if isinstance(xtmp, np.ndarray): 

2337 return xtmp 

2338 return x