##// END OF EJS Templates
Fix typos
Grant Nestor -
Show More
@@ -1,1373 +1,1373 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Top-level display functions for displaying object in different formats."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 try:
9 9 from base64 import encodebytes as base64_encode
10 10 except ImportError:
11 11 from base64 import encodestring as base64_encode
12 12
13 13 from binascii import b2a_hex
14 14 import json
15 15 import mimetypes
16 16 import os
17 17 import struct
18 18 import sys
19 19 import warnings
20 20 from copy import deepcopy
21 21
22 22 from IPython.utils.py3compat import cast_unicode
23 23 from IPython.testing.skipdoctest import skip_doctest
24 24
25 25 __all__ = ['display', 'display_pretty', 'display_html', 'display_markdown',
26 26 'display_svg', 'display_png', 'display_jpeg', 'display_gif', 'display_latex', 'display_json',
27 27 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject',
28 28 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'JSON', 'GeoJSON', 'Javascript',
29 29 'Image', 'clear_output', 'set_matplotlib_formats', 'set_matplotlib_close',
30 30 'publish_display_data', 'update_display', 'DisplayHandle', 'Video']
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # utility functions
34 34 #-----------------------------------------------------------------------------
35 35
36 36 def _safe_exists(path):
37 37 """Check path, but don't let exceptions raise"""
38 38 try:
39 39 return os.path.exists(path)
40 40 except Exception:
41 41 return False
42 42
43 43 def _merge(d1, d2):
44 44 """Like update, but merges sub-dicts instead of clobbering at the top level.
45 45
46 46 Updates d1 in-place
47 47 """
48 48
49 49 if not isinstance(d2, dict) or not isinstance(d1, dict):
50 50 return d2
51 51 for key, value in d2.items():
52 52 d1[key] = _merge(d1.get(key), value)
53 53 return d1
54 54
55 55 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
56 56 """internal implementation of all display_foo methods
57 57
58 58 Parameters
59 59 ----------
60 60 mimetype : str
61 61 The mimetype to be published (e.g. 'image/png')
62 62 objs : tuple of objects
63 63 The Python objects to display, or if raw=True raw text data to
64 64 display.
65 65 raw : bool
66 66 Are the data objects raw data or Python objects that need to be
67 67 formatted before display? [default: False]
68 68 metadata : dict (optional)
69 69 Metadata to be associated with the specific mimetype output.
70 70 """
71 71 if metadata:
72 72 metadata = {mimetype: metadata}
73 73 if raw:
74 74 # turn list of pngdata into list of { 'image/png': pngdata }
75 75 objs = [ {mimetype: obj} for obj in objs ]
76 76 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
77 77
78 78 #-----------------------------------------------------------------------------
79 79 # Main functions
80 80 #-----------------------------------------------------------------------------
81 81
82 82 # use * to indicate transient is keyword-only
83 83 def publish_display_data(data, metadata=None, source=None, *, transient=None, **kwargs):
84 84 """Publish data and metadata to all frontends.
85 85
86 86 See the ``display_data`` message in the messaging documentation for
87 87 more details about this message type.
88 88
89 89 The following MIME types are currently implemented:
90 90
91 91 * text/plain
92 92 * text/html
93 93 * text/markdown
94 94 * text/latex
95 95 * application/json
96 96 * application/javascript
97 97 * image/png
98 98 * image/jpeg
99 99 * image/gif
100 100 * image/svg+xml
101 101
102 102 Parameters
103 103 ----------
104 104 data : dict
105 105 A dictionary having keys that are valid MIME types (like
106 106 'text/plain' or 'image/svg+xml') and values that are the data for
107 107 that MIME type. The data itself must be a JSON'able data
108 108 structure. Minimally all data should have the 'text/plain' data,
109 109 which can be displayed by all frontends. If more than the plain
110 110 text is given, it is up to the frontend to decide which
111 111 representation to use.
112 112 metadata : dict
113 113 A dictionary for metadata related to the data. This can contain
114 114 arbitrary key, value pairs that frontends can use to interpret
115 115 the data. mime-type keys matching those in data can be used
116 116 to specify metadata about particular representations.
117 117 source : str, deprecated
118 118 Unused.
119 119 transient : dict, keyword-only
120 120 A dictionary of transient data, such as display_id.
121 121 """
122 122 from IPython.core.interactiveshell import InteractiveShell
123 123
124 124 display_pub = InteractiveShell.instance().display_pub
125 125
126 126 # only pass transient if supplied,
127 127 # to avoid errors with older ipykernel.
128 128 # TODO: We could check for ipykernel version and provide a detailed upgrade message.
129 129 if transient:
130 130 kwargs['transient'] = transient
131 131
132 132 display_pub.publish(
133 133 data=data,
134 134 metadata=metadata,
135 135 **kwargs
136 136 )
137 137
138 138
139 139 def _new_id():
140 140 """Generate a new random text id with urandom"""
141 141 return b2a_hex(os.urandom(16)).decode('ascii')
142 142
143 143
144 144 def display(*objs, include=None, exclude=None, metadata=None, transient=None, display_id=None, **kwargs):
145 145 """Display a Python object in all frontends.
146 146
147 147 By default all representations will be computed and sent to the frontends.
148 148 Frontends can decide which representation is used and how.
149 149
150 150 In terminal IPython this will be similar to using :func:`print`, for use in richer
151 151 frontends see Jupyter notebook examples with rich display logic.
152 152
153 153 Parameters
154 154 ----------
155 155 objs : tuple of objects
156 156 The Python objects to display.
157 157 raw : bool, optional
158 158 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
159 159 or Python objects that need to be formatted before display? [default: False]
160 160 include : list, tuple or set, optional
161 161 A list of format type strings (MIME types) to include in the
162 162 format data dict. If this is set *only* the format types included
163 163 in this list will be computed.
164 164 exclude : list, tuple or set, optional
165 165 A list of format type strings (MIME types) to exclude in the format
166 166 data dict. If this is set all format types will be computed,
167 167 except for those included in this argument.
168 168 metadata : dict, optional
169 169 A dictionary of metadata to associate with the output.
170 170 mime-type keys in this dictionary will be associated with the individual
171 171 representation formats, if they exist.
172 172 transient : dict, optional
173 173 A dictionary of transient data to associate with the output.
174 174 Data in this dict should not be persisted to files (e.g. notebooks).
175 175 display_id : str, bool optional
176 176 Set an id for the display.
177 177 This id can be used for updating this display area later via update_display.
178 178 If given as `True`, generate a new `display_id`
179 179 kwargs: additional keyword-args, optional
180 180 Additional keyword-arguments are passed through to the display publisher.
181 181
182 182 Returns
183 183 -------
184 184
185 185 handle: DisplayHandle
186 186 Returns a handle on updatable displays for use with :func:`update_display`,
187 187 if `display_id` is given. Returns :any:`None` if no `display_id` is given
188 188 (default).
189 189
190 190 Examples
191 191 --------
192 192
193 193 >>> class Json(object):
194 194 ... def __init__(self, json):
195 195 ... self.json = json
196 196 ... def _repr_pretty_(self, pp, cycle):
197 197 ... import json
198 198 ... pp.text(json.dumps(self.json, indent=2))
199 199 ... def __repr__(self):
200 200 ... return str(self.json)
201 201 ...
202 202
203 203 >>> d = Json({1:2, 3: {4:5}})
204 204
205 205 >>> print(d)
206 206 {1: 2, 3: {4: 5}}
207 207
208 208 >>> display(d)
209 209 {
210 210 "1": 2,
211 211 "3": {
212 212 "4": 5
213 213 }
214 214 }
215 215
216 216 >>> def int_formatter(integer, pp, cycle):
217 217 ... pp.text('I'*integer)
218 218
219 219 >>> plain = get_ipython().display_formatter.formatters['text/plain']
220 220 >>> plain.for_type(int, int_formatter)
221 221 <function _repr_pprint at 0x...>
222 222 >>> display(7-5)
223 223 II
224 224
225 225 >>> del plain.type_printers[int]
226 226 >>> display(7-5)
227 227 2
228 228
229 229 See Also
230 230 --------
231 231
232 232 :func:`update_display`
233 233
234 234 Notes
235 235 -----
236 236
237 237 In Python, objects can declare their textual representation using the
238 238 `__repr__` method. IPython expands on this idea and allows objects to declare
239 239 other, rich representations including:
240 240
241 241 - HTML
242 242 - JSON
243 243 - PNG
244 244 - JPEG
245 245 - SVG
246 246 - LaTeX
247 247
248 248 A single object can declare some or all of these representations; all are
249 249 handled by IPython's display system.
250 250
251 251 The main idea of the first approach is that you have to implement special
252 252 display methods when you define your class, one for each representation you
253 253 want to use. Here is a list of the names of the special methods and the
254 254 values they must return:
255 255
256 256 - `_repr_html_`: return raw HTML as a string
257 257 - `_repr_json_`: return a JSONable dict
258 258 - `_repr_jpeg_`: return raw JPEG data
259 259 - `_repr_png_`: return raw PNG data
260 - `_repr_gif_`: return raw PNG data
260 - `_repr_gif_`: return raw GIF data
261 261 - `_repr_svg_`: return raw SVG data as a string
262 262 - `_repr_latex_`: return LaTeX commands in a string surrounded by "$".
263 263 - `_repr_mimebundle_`: return a full mimebundle containing the mapping
264 264 from all mimetypes to data
265 265
266 266 When you are directly writing your own classes, you can adapt them for
267 267 display in IPython by following the above approach. But in practice, you
268 268 often need to work with existing classes that you can't easily modify.
269 269
270 270 You can refer to the documentation on IPython display formatters in order to
271 271 register custom formatters for already existing types.
272 272
273 273 .. versionadded:: 5.4 display available without import
274 274 .. versionadded:: 6.1 display available without import
275 275
276 276 Since IPython 5.4 and 6.1 :func:`display` is automatically made available to
277 277 the user without import. If you are using display in a document that might
278 278 be used in a pure python context or with older version of IPython, use the
279 279 following import at the top of your file::
280 280
281 281 from IPython.display import display
282 282
283 283 """
284 284 from IPython.core.interactiveshell import InteractiveShell
285 285
286 286 if not InteractiveShell.initialized():
287 287 # Directly print objects.
288 288 print(*objs)
289 289 return
290 290
291 291 raw = kwargs.pop('raw', False)
292 292 if transient is None:
293 293 transient = {}
294 294 if metadata is None:
295 295 metadata={}
296 296 if display_id:
297 297 if display_id is True:
298 298 display_id = _new_id()
299 299 transient['display_id'] = display_id
300 300 if kwargs.get('update') and 'display_id' not in transient:
301 301 raise TypeError('display_id required for update_display')
302 302 if transient:
303 303 kwargs['transient'] = transient
304 304
305 305 if not raw:
306 306 format = InteractiveShell.instance().display_formatter.format
307 307
308 308 for obj in objs:
309 309 if raw:
310 310 publish_display_data(data=obj, metadata=metadata, **kwargs)
311 311 else:
312 312 format_dict, md_dict = format(obj, include=include, exclude=exclude)
313 313 if not format_dict:
314 314 # nothing to display (e.g. _ipython_display_ took over)
315 315 continue
316 316 if metadata:
317 317 # kwarg-specified metadata gets precedence
318 318 _merge(md_dict, metadata)
319 319 publish_display_data(data=format_dict, metadata=md_dict, **kwargs)
320 320 if display_id:
321 321 return DisplayHandle(display_id)
322 322
323 323
324 324 # use * for keyword-only display_id arg
325 325 def update_display(obj, *, display_id, **kwargs):
326 326 """Update an existing display by id
327 327
328 328 Parameters
329 329 ----------
330 330
331 331 obj:
332 332 The object with which to update the display
333 333 display_id: keyword-only
334 334 The id of the display to update
335 335
336 336 See Also
337 337 --------
338 338
339 339 :func:`display`
340 340 """
341 341 kwargs['update'] = True
342 342 display(obj, display_id=display_id, **kwargs)
343 343
344 344
345 345 class DisplayHandle(object):
346 346 """A handle on an updatable display
347 347
348 348 Call `.update(obj)` to display a new object.
349 349
350 350 Call `.display(obj`) to add a new instance of this display,
351 351 and update existing instances.
352 352
353 353 See Also
354 354 --------
355 355
356 356 :func:`display`, :func:`update_display`
357 357
358 358 """
359 359
360 360 def __init__(self, display_id=None):
361 361 if display_id is None:
362 362 display_id = _new_id()
363 363 self.display_id = display_id
364 364
365 365 def __repr__(self):
366 366 return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id)
367 367
368 368 def display(self, obj, **kwargs):
369 369 """Make a new display with my id, updating existing instances.
370 370
371 371 Parameters
372 372 ----------
373 373
374 374 obj:
375 375 object to display
376 376 **kwargs:
377 377 additional keyword arguments passed to display
378 378 """
379 379 display(obj, display_id=self.display_id, **kwargs)
380 380
381 381 def update(self, obj, **kwargs):
382 382 """Update existing displays with my id
383 383
384 384 Parameters
385 385 ----------
386 386
387 387 obj:
388 388 object to display
389 389 **kwargs:
390 390 additional keyword arguments passed to update_display
391 391 """
392 392 update_display(obj, display_id=self.display_id, **kwargs)
393 393
394 394
395 395 def display_pretty(*objs, **kwargs):
396 396 """Display the pretty (default) representation of an object.
397 397
398 398 Parameters
399 399 ----------
400 400 objs : tuple of objects
401 401 The Python objects to display, or if raw=True raw text data to
402 402 display.
403 403 raw : bool
404 404 Are the data objects raw data or Python objects that need to be
405 405 formatted before display? [default: False]
406 406 metadata : dict (optional)
407 407 Metadata to be associated with the specific mimetype output.
408 408 """
409 409 _display_mimetype('text/plain', objs, **kwargs)
410 410
411 411
412 412 def display_html(*objs, **kwargs):
413 413 """Display the HTML representation of an object.
414 414
415 415 Note: If raw=False and the object does not have a HTML
416 416 representation, no HTML will be shown.
417 417
418 418 Parameters
419 419 ----------
420 420 objs : tuple of objects
421 421 The Python objects to display, or if raw=True raw HTML data to
422 422 display.
423 423 raw : bool
424 424 Are the data objects raw data or Python objects that need to be
425 425 formatted before display? [default: False]
426 426 metadata : dict (optional)
427 427 Metadata to be associated with the specific mimetype output.
428 428 """
429 429 _display_mimetype('text/html', objs, **kwargs)
430 430
431 431
432 432 def display_markdown(*objs, **kwargs):
433 433 """Displays the Markdown representation of an object.
434 434
435 435 Parameters
436 436 ----------
437 437 objs : tuple of objects
438 438 The Python objects to display, or if raw=True raw markdown data to
439 439 display.
440 440 raw : bool
441 441 Are the data objects raw data or Python objects that need to be
442 442 formatted before display? [default: False]
443 443 metadata : dict (optional)
444 444 Metadata to be associated with the specific mimetype output.
445 445 """
446 446
447 447 _display_mimetype('text/markdown', objs, **kwargs)
448 448
449 449
450 450 def display_svg(*objs, **kwargs):
451 451 """Display the SVG representation of an object.
452 452
453 453 Parameters
454 454 ----------
455 455 objs : tuple of objects
456 456 The Python objects to display, or if raw=True raw svg data to
457 457 display.
458 458 raw : bool
459 459 Are the data objects raw data or Python objects that need to be
460 460 formatted before display? [default: False]
461 461 metadata : dict (optional)
462 462 Metadata to be associated with the specific mimetype output.
463 463 """
464 464 _display_mimetype('image/svg+xml', objs, **kwargs)
465 465
466 466
467 467 def display_png(*objs, **kwargs):
468 468 """Display the PNG representation of an object.
469 469
470 470 Parameters
471 471 ----------
472 472 objs : tuple of objects
473 473 The Python objects to display, or if raw=True raw png data to
474 474 display.
475 475 raw : bool
476 476 Are the data objects raw data or Python objects that need to be
477 477 formatted before display? [default: False]
478 478 metadata : dict (optional)
479 479 Metadata to be associated with the specific mimetype output.
480 480 """
481 481 _display_mimetype('image/png', objs, **kwargs)
482 482
483 483
484 484 def display_jpeg(*objs, **kwargs):
485 485 """Display the JPEG representation of an object.
486 486
487 487 Parameters
488 488 ----------
489 489 objs : tuple of objects
490 490 The Python objects to display, or if raw=True raw JPEG data to
491 491 display.
492 492 raw : bool
493 493 Are the data objects raw data or Python objects that need to be
494 494 formatted before display? [default: False]
495 495 metadata : dict (optional)
496 496 Metadata to be associated with the specific mimetype output.
497 497 """
498 498 _display_mimetype('image/jpeg', objs, **kwargs)
499 499
500 500 def display_gif(*objs, **kwargs):
501 501 """Display the GIF representation of an object.
502 502
503 503 Parameters
504 504 ----------
505 505 objs : tuple of objects
506 506 The Python objects to display, or if raw=True raw gif data to
507 507 display.
508 508 raw : bool
509 509 Are the data objects raw data or Python objects that need to be
510 510 formatted before display? [default: False]
511 511 metadata : dict (optional)
512 512 Metadata to be associated with the specific mimetype output.
513 513 """
514 514 _display_mimetype('image/gif', objs, **kwargs)
515 515
516 516
517 517 def display_latex(*objs, **kwargs):
518 518 """Display the LaTeX representation of an object.
519 519
520 520 Parameters
521 521 ----------
522 522 objs : tuple of objects
523 523 The Python objects to display, or if raw=True raw latex data to
524 524 display.
525 525 raw : bool
526 526 Are the data objects raw data or Python objects that need to be
527 527 formatted before display? [default: False]
528 528 metadata : dict (optional)
529 529 Metadata to be associated with the specific mimetype output.
530 530 """
531 531 _display_mimetype('text/latex', objs, **kwargs)
532 532
533 533
534 534 def display_json(*objs, **kwargs):
535 535 """Display the JSON representation of an object.
536 536
537 537 Note that not many frontends support displaying JSON.
538 538
539 539 Parameters
540 540 ----------
541 541 objs : tuple of objects
542 542 The Python objects to display, or if raw=True raw json data to
543 543 display.
544 544 raw : bool
545 545 Are the data objects raw data or Python objects that need to be
546 546 formatted before display? [default: False]
547 547 metadata : dict (optional)
548 548 Metadata to be associated with the specific mimetype output.
549 549 """
550 550 _display_mimetype('application/json', objs, **kwargs)
551 551
552 552
553 553 def display_javascript(*objs, **kwargs):
554 554 """Display the Javascript representation of an object.
555 555
556 556 Parameters
557 557 ----------
558 558 objs : tuple of objects
559 559 The Python objects to display, or if raw=True raw javascript data to
560 560 display.
561 561 raw : bool
562 562 Are the data objects raw data or Python objects that need to be
563 563 formatted before display? [default: False]
564 564 metadata : dict (optional)
565 565 Metadata to be associated with the specific mimetype output.
566 566 """
567 567 _display_mimetype('application/javascript', objs, **kwargs)
568 568
569 569
570 570 def display_pdf(*objs, **kwargs):
571 571 """Display the PDF representation of an object.
572 572
573 573 Parameters
574 574 ----------
575 575 objs : tuple of objects
576 576 The Python objects to display, or if raw=True raw javascript data to
577 577 display.
578 578 raw : bool
579 579 Are the data objects raw data or Python objects that need to be
580 580 formatted before display? [default: False]
581 581 metadata : dict (optional)
582 582 Metadata to be associated with the specific mimetype output.
583 583 """
584 584 _display_mimetype('application/pdf', objs, **kwargs)
585 585
586 586
587 587 #-----------------------------------------------------------------------------
588 588 # Smart classes
589 589 #-----------------------------------------------------------------------------
590 590
591 591
592 592 class DisplayObject(object):
593 593 """An object that wraps data to be displayed."""
594 594
595 595 _read_flags = 'r'
596 596 _show_mem_addr = False
597 597 metadata = None
598 598
599 599 def __init__(self, data=None, url=None, filename=None, metadata=None):
600 600 """Create a display object given raw data.
601 601
602 602 When this object is returned by an expression or passed to the
603 603 display function, it will result in the data being displayed
604 604 in the frontend. The MIME type of the data should match the
605 605 subclasses used, so the Png subclass should be used for 'image/png'
606 606 data. If the data is a URL, the data will first be downloaded
607 607 and then displayed. If
608 608
609 609 Parameters
610 610 ----------
611 611 data : unicode, str or bytes
612 612 The raw data or a URL or file to load the data from
613 613 url : unicode
614 614 A URL to download the data from.
615 615 filename : unicode
616 616 Path to a local file to load the data from.
617 617 metadata : dict
618 618 Dict of metadata associated to be the object when displayed
619 619 """
620 620 if data is not None and isinstance(data, str):
621 621 if data.startswith('http') and url is None:
622 622 url = data
623 623 filename = None
624 624 data = None
625 625 elif _safe_exists(data) and filename is None:
626 626 url = None
627 627 filename = data
628 628 data = None
629 629
630 630 self.data = data
631 631 self.url = url
632 632 self.filename = filename
633 633
634 634 if metadata is not None:
635 635 self.metadata = metadata
636 636 elif self.metadata is None:
637 637 self.metadata = {}
638 638
639 639 self.reload()
640 640 self._check_data()
641 641
642 642 def __repr__(self):
643 643 if not self._show_mem_addr:
644 644 cls = self.__class__
645 645 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
646 646 else:
647 647 r = super(DisplayObject, self).__repr__()
648 648 return r
649 649
650 650 def _check_data(self):
651 651 """Override in subclasses if there's something to check."""
652 652 pass
653 653
654 654 def _data_and_metadata(self):
655 655 """shortcut for returning metadata with shape information, if defined"""
656 656 if self.metadata:
657 657 return self.data, deepcopy(self.metadata)
658 658 else:
659 659 return self.data
660 660
661 661 def reload(self):
662 662 """Reload the raw data from file or URL."""
663 663 if self.filename is not None:
664 664 with open(self.filename, self._read_flags) as f:
665 665 self.data = f.read()
666 666 elif self.url is not None:
667 667 try:
668 668 # Deferred import
669 669 from urllib.request import urlopen
670 670 response = urlopen(self.url)
671 671 self.data = response.read()
672 672 # extract encoding from header, if there is one:
673 673 encoding = None
674 674 for sub in response.headers['content-type'].split(';'):
675 675 sub = sub.strip()
676 676 if sub.startswith('charset'):
677 677 encoding = sub.split('=')[-1].strip()
678 678 break
679 679 # decode data, if an encoding was specified
680 680 if encoding:
681 681 self.data = self.data.decode(encoding, 'replace')
682 682 except:
683 683 self.data = None
684 684
685 685 class TextDisplayObject(DisplayObject):
686 686 """Validate that display data is text"""
687 687 def _check_data(self):
688 688 if self.data is not None and not isinstance(self.data, str):
689 689 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
690 690
691 691 class Pretty(TextDisplayObject):
692 692
693 693 def _repr_pretty_(self, pp, cycle):
694 694 return pp.text(self.data)
695 695
696 696
697 697 class HTML(TextDisplayObject):
698 698
699 699 def _repr_html_(self):
700 700 return self.data
701 701
702 702 def __html__(self):
703 703 """
704 704 This method exists to inform other HTML-using modules (e.g. Markupsafe,
705 705 htmltag, etc) that this object is HTML and does not need things like
706 706 special characters (<>&) escaped.
707 707 """
708 708 return self._repr_html_()
709 709
710 710
711 711 class Markdown(TextDisplayObject):
712 712
713 713 def _repr_markdown_(self):
714 714 return self.data
715 715
716 716
717 717 class Math(TextDisplayObject):
718 718
719 719 def _repr_latex_(self):
720 720 s = self.data.strip('$')
721 721 return "$$%s$$" % s
722 722
723 723
724 724 class Latex(TextDisplayObject):
725 725
726 726 def _repr_latex_(self):
727 727 return self.data
728 728
729 729
730 730 class SVG(DisplayObject):
731 731
732 732 _read_flags = 'rb'
733 733 # wrap data in a property, which extracts the <svg> tag, discarding
734 734 # document headers
735 735 _data = None
736 736
737 737 @property
738 738 def data(self):
739 739 return self._data
740 740
741 741 @data.setter
742 742 def data(self, svg):
743 743 if svg is None:
744 744 self._data = None
745 745 return
746 746 # parse into dom object
747 747 from xml.dom import minidom
748 748 x = minidom.parseString(svg)
749 749 # get svg tag (should be 1)
750 750 found_svg = x.getElementsByTagName('svg')
751 751 if found_svg:
752 752 svg = found_svg[0].toxml()
753 753 else:
754 754 # fallback on the input, trust the user
755 755 # but this is probably an error.
756 756 pass
757 757 svg = cast_unicode(svg)
758 758 self._data = svg
759 759
760 760 def _repr_svg_(self):
761 761 return self._data_and_metadata()
762 762
763 763
764 764 class JSON(DisplayObject):
765 765 """JSON expects a JSON-able dict or list
766 766
767 767 not an already-serialized JSON string.
768 768
769 769 Scalar types (None, number, string) are not allowed, only dict or list containers.
770 770 """
771 771 # wrap data in a property, which warns about passing already-serialized JSON
772 772 _data = None
773 773 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, **kwargs):
774 774 """Create a JSON display object given raw data.
775 775
776 776 Parameters
777 777 ----------
778 778 data : dict or list
779 779 JSON data to display. Not an already-serialized JSON string.
780 780 Scalar types (None, number, string) are not allowed, only dict
781 781 or list containers.
782 782 url : unicode
783 783 A URL to download the data from.
784 784 filename : unicode
785 785 Path to a local file to load the data from.
786 786 expanded : boolean
787 787 Metadata to control whether a JSON display component is expanded.
788 788 metadata: dict
789 789 Specify extra metadata to attach to the json display object.
790 790 """
791 791 self.metadata = {'expanded': expanded}
792 792 if metadata:
793 793 self.metadata.update(metadata)
794 794 if kwargs:
795 795 self.metadata.update(kwargs)
796 796 super(JSON, self).__init__(data=data, url=url, filename=filename)
797 797
798 798 def _check_data(self):
799 799 if self.data is not None and not isinstance(self.data, (dict, list)):
800 800 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
801 801
802 802 @property
803 803 def data(self):
804 804 return self._data
805 805
806 806 @data.setter
807 807 def data(self, data):
808 808 if isinstance(data, str):
809 809 if getattr(self, 'filename', None) is None:
810 810 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
811 811 data = json.loads(data)
812 812 self._data = data
813 813
814 814 def _data_and_metadata(self):
815 815 return self.data, self.metadata
816 816
817 817 def _repr_json_(self):
818 818 return self._data_and_metadata()
819 819
820 820 _css_t = """$("head").append($("<link/>").attr({
821 821 rel: "stylesheet",
822 822 type: "text/css",
823 823 href: "%s"
824 824 }));
825 825 """
826 826
827 827 _lib_t1 = """$.getScript("%s", function () {
828 828 """
829 829 _lib_t2 = """});
830 830 """
831 831
832 832 class GeoJSON(JSON):
833 833 """GeoJSON expects JSON-able dict
834 834
835 835 not an already-serialized JSON string.
836 836
837 837 Scalar types (None, number, string) are not allowed, only dict containers.
838 838 """
839 839
840 840 def __init__(self, *args, **kwargs):
841 841 """Create a GeoJSON display object given raw data.
842 842
843 843 Parameters
844 844 ----------
845 845 data : dict or list
846 846 VegaLite data. Not an already-serialized JSON string.
847 847 Scalar types (None, number, string) are not allowed, only dict
848 848 or list containers.
849 849 url_template : string
850 850 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
851 851 layer_options : dict
852 852 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
853 853 url : unicode
854 854 A URL to download the data from.
855 855 filename : unicode
856 856 Path to a local file to load the data from.
857 857 metadata: dict
858 858 Specify extra metadata to attach to the json display object.
859 859
860 860 Examples
861 861 --------
862 862
863 863 The following will display an interactive map of Mars with a point of
864 864 interest on frontend that do support GeoJSON display.
865 865
866 866 >>> from IPython.display import GeoJSON
867 867
868 868 >>> GeoJSON(data={
869 869 ... "type": "Feature",
870 870 ... "geometry": {
871 871 ... "type": "Point",
872 872 ... "coordinates": [-81.327, 296.038]
873 873 ... }
874 874 ... },
875 875 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
876 876 ... layer_options={
877 877 ... "basemap_id": "celestia_mars-shaded-16k_global",
878 878 ... "attribution" : "Celestia/praesepe",
879 879 ... "minZoom" : 0,
880 880 ... "maxZoom" : 18,
881 881 ... })
882 882 <IPython.core.display.GeoJSON object>
883 883
884 884 In the terminal IPython, you will only see the text representation of
885 885 the GeoJSON object.
886 886
887 887 """
888 888
889 889 super(GeoJSON, self).__init__(*args, **kwargs)
890 890
891 891
892 892 def _ipython_display_(self):
893 893 bundle = {
894 894 'application/geo+json': self.data,
895 895 'text/plain': '<IPython.display.GeoJSON object>'
896 896 }
897 897 metadata = {
898 898 'application/geo+json': self.metadata
899 899 }
900 900 display(bundle, metadata=metadata, raw=True)
901 901
902 902 class Javascript(TextDisplayObject):
903 903
904 904 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
905 905 """Create a Javascript display object given raw data.
906 906
907 907 When this object is returned by an expression or passed to the
908 908 display function, it will result in the data being displayed
909 909 in the frontend. If the data is a URL, the data will first be
910 910 downloaded and then displayed.
911 911
912 912 In the Notebook, the containing element will be available as `element`,
913 913 and jQuery will be available. Content appended to `element` will be
914 914 visible in the output area.
915 915
916 916 Parameters
917 917 ----------
918 918 data : unicode, str or bytes
919 919 The Javascript source code or a URL to download it from.
920 920 url : unicode
921 921 A URL to download the data from.
922 922 filename : unicode
923 923 Path to a local file to load the data from.
924 924 lib : list or str
925 925 A sequence of Javascript library URLs to load asynchronously before
926 926 running the source code. The full URLs of the libraries should
927 927 be given. A single Javascript library URL can also be given as a
928 928 string.
929 929 css: : list or str
930 930 A sequence of css files to load before running the source code.
931 931 The full URLs of the css files should be given. A single css URL
932 932 can also be given as a string.
933 933 """
934 934 if isinstance(lib, str):
935 935 lib = [lib]
936 936 elif lib is None:
937 937 lib = []
938 938 if isinstance(css, str):
939 939 css = [css]
940 940 elif css is None:
941 941 css = []
942 942 if not isinstance(lib, (list,tuple)):
943 943 raise TypeError('expected sequence, got: %r' % lib)
944 944 if not isinstance(css, (list,tuple)):
945 945 raise TypeError('expected sequence, got: %r' % css)
946 946 self.lib = lib
947 947 self.css = css
948 superg(Javascript, self).__init__(data=data, url=url, filename=filename)
948 super(Javascript, self).__init__(data=data, url=url, filename=filename)
949 949
950 950 def _repr_javascript_(self):
951 951 r = ''
952 952 for c in self.css:
953 953 r += _css_t % c
954 954 for l in self.lib:
955 955 r += _lib_t1 % l
956 956 r += self.data
957 957 r += _lib_t2*len(self.lib)
958 958 return r
959 959
960 960 # constants for identifying png/jpeg data
961 961 _PNG = b'\x89PNG\r\n\x1a\n'
962 962 _JPEG = b'\xff\xd8'
963 963
964 964 def _pngxy(data):
965 965 """read the (width, height) from a PNG header"""
966 966 ihdr = data.index(b'IHDR')
967 967 # next 8 bytes are width/height
968 968 w4h4 = data[ihdr+4:ihdr+12]
969 969 return struct.unpack('>ii', w4h4)
970 970
971 971 def _jpegxy(data):
972 972 """read the (width, height) from a JPEG header"""
973 973 # adapted from http://www.64lines.com/jpeg-width-height
974 974
975 975 idx = 4
976 976 while True:
977 977 block_size = struct.unpack('>H', data[idx:idx+2])[0]
978 978 idx = idx + block_size
979 979 if data[idx:idx+2] == b'\xFF\xC0':
980 980 # found Start of Frame
981 981 iSOF = idx
982 982 break
983 983 else:
984 984 # read another block
985 985 idx += 2
986 986
987 987 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
988 988 return w, h
989 989
990 990 class Image(DisplayObject):
991 991
992 992 _read_flags = 'rb'
993 993 _FMT_JPEG = u'jpeg'
994 994 _FMT_PNG = u'png'
995 995 _FMT_GIF = u'gif'
996 996 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF]
997 997
998 998 def __init__(self, data=None, url=None, filename=None, format=None,
999 999 embed=None, width=None, height=None, retina=False,
1000 1000 unconfined=False, metadata=None):
1001 1001 """Create a PNG/JPEG/GIF image object given raw data.
1002 1002
1003 1003 When this object is returned by an input cell or passed to the
1004 1004 display function, it will result in the image being displayed
1005 1005 in the frontend.
1006 1006
1007 1007 Parameters
1008 1008 ----------
1009 1009 data : unicode, str or bytes
1010 1010 The raw image data or a URL or filename to load the data from.
1011 1011 This always results in embedded image data.
1012 1012 url : unicode
1013 1013 A URL to download the data from. If you specify `url=`,
1014 1014 the image data will not be embedded unless you also specify `embed=True`.
1015 1015 filename : unicode
1016 1016 Path to a local file to load the data from.
1017 1017 Images from a file are always embedded.
1018 1018 format : unicode
1019 1019 The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given
1020 1020 for format will be inferred from the filename extension.
1021 1021 embed : bool
1022 1022 Should the image data be embedded using a data URI (True) or be
1023 1023 loaded using an <img> tag. Set this to True if you want the image
1024 1024 to be viewable later with no internet connection in the notebook.
1025 1025
1026 1026 Default is `True`, unless the keyword argument `url` is set, then
1027 1027 default value is `False`.
1028 1028
1029 1029 Note that QtConsole is not able to display images if `embed` is set to `False`
1030 1030 width : int
1031 1031 Width in pixels to which to constrain the image in html
1032 1032 height : int
1033 1033 Height in pixels to which to constrain the image in html
1034 1034 retina : bool
1035 1035 Automatically set the width and height to half of the measured
1036 1036 width and height.
1037 1037 This only works for embedded images because it reads the width/height
1038 1038 from image data.
1039 1039 For non-embedded images, you can just set the desired display width
1040 1040 and height directly.
1041 1041 unconfined: bool
1042 1042 Set unconfined=True to disable max-width confinement of the image.
1043 1043 metadata: dict
1044 1044 Specify extra metadata to attach to the image.
1045 1045
1046 1046 Examples
1047 1047 --------
1048 1048 # embedded image data, works in qtconsole and notebook
1049 1049 # when passed positionally, the first arg can be any of raw image data,
1050 1050 # a URL, or a filename from which to load image data.
1051 1051 # The result is always embedding image data for inline images.
1052 1052 Image('http://www.google.fr/images/srpr/logo3w.png')
1053 1053 Image('/path/to/image.jpg')
1054 1054 Image(b'RAW_PNG_DATA...')
1055 1055
1056 1056 # Specifying Image(url=...) does not embed the image data,
1057 1057 # it only generates `<img>` tag with a link to the source.
1058 1058 # This will not work in the qtconsole or offline.
1059 1059 Image(url='http://www.google.fr/images/srpr/logo3w.png')
1060 1060
1061 1061 """
1062 1062 if filename is not None:
1063 1063 ext = self._find_ext(filename)
1064 1064 elif url is not None:
1065 1065 ext = self._find_ext(url)
1066 1066 elif data is None:
1067 1067 raise ValueError("No image data found. Expecting filename, url, or data.")
1068 1068 elif isinstance(data, str) and (
1069 1069 data.startswith('http') or _safe_exists(data)
1070 1070 ):
1071 1071 ext = self._find_ext(data)
1072 1072 else:
1073 1073 ext = None
1074 1074
1075 1075 if format is None:
1076 1076 if ext is not None:
1077 1077 if ext == u'jpg' or ext == u'jpeg':
1078 1078 format = self._FMT_JPEG
1079 1079 if ext == u'png':
1080 1080 format = self._FMT_PNG
1081 1081 if ext == u'gif':
1082 1082 # use PNG format until we understand why GIF is not working
1083 1083 format = self._FMT_PNG
1084 1084 else:
1085 1085 format = ext.lower()
1086 1086 elif isinstance(data, bytes):
1087 1087 # infer image type from image data header,
1088 1088 # only if format has not been specified.
1089 1089 if data[:2] == _JPEG:
1090 1090 format = self._FMT_JPEG
1091 1091
1092 1092 # failed to detect format, default png
1093 1093 if format is None:
1094 1094 format = self._FMT_PNG
1095 1095
1096 1096 if format.lower() == 'jpg':
1097 1097 # jpg->jpeg
1098 1098 format = self._FMT_JPEG
1099 1099
1100 1100 if format == self._FMT_GIF:
1101 1101 # use PNG format until we understand why GIF is not working
1102 1102 format = self._FMT_PNG
1103 1103
1104 1104 self.format = format.lower()
1105 1105 self.embed = embed if embed is not None else (url is None)
1106 1106
1107 1107 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
1108 1108 raise ValueError("Cannot embed the '%s' image format" % (self.format))
1109 1109 self.width = width
1110 1110 self.height = height
1111 1111 self.retina = retina
1112 1112 self.unconfined = unconfined
1113 1113 super(Image, self).__init__(data=data, url=url, filename=filename,
1114 1114 metadata=metadata)
1115 1115
1116 1116 if self.width is None and self.metadata.get('width', {}):
1117 1117 self.width = metadata['width']
1118 1118
1119 1119 if self.height is None and self.metadata.get('height', {}):
1120 1120 self.height = metadata['height']
1121 1121
1122 1122 if retina:
1123 1123 self._retina_shape()
1124 1124
1125 1125 def _retina_shape(self):
1126 1126 """load pixel-doubled width and height from image data"""
1127 1127 if not self.embed:
1128 1128 return
1129 1129 if self.format == self._FMT_PNG:
1130 1130 w, h = _pngxy(self.data)
1131 1131 elif self.format == self._FMT_JPEG:
1132 1132 w, h = _jpegxy(self.data)
1133 1133 else:
1134 1134 # retina only supports png
1135 1135 return
1136 1136 self.width = w // 2
1137 1137 self.height = h // 2
1138 1138
1139 1139 def reload(self):
1140 1140 """Reload the raw data from file or URL."""
1141 1141 if self.embed:
1142 1142 super(Image,self).reload()
1143 1143 if self.retina:
1144 1144 self._retina_shape()
1145 1145
1146 1146 def _repr_html_(self):
1147 1147 if not self.embed:
1148 1148 width = height = klass = ''
1149 1149 if self.width:
1150 1150 width = ' width="%d"' % self.width
1151 1151 if self.height:
1152 1152 height = ' height="%d"' % self.height
1153 1153 if self.unconfined:
1154 1154 klass = ' class="unconfined"'
1155 1155 return u'<img src="{url}"{width}{height}{klass}/>'.format(
1156 1156 url=self.url,
1157 1157 width=width,
1158 1158 height=height,
1159 1159 klass=klass,
1160 1160 )
1161 1161
1162 1162 def _data_and_metadata(self):
1163 1163 """shortcut for returning metadata with shape information, if defined"""
1164 1164 md = {}
1165 1165 if self.metadata:
1166 1166 md.update(self.metadata)
1167 1167 if self.width:
1168 1168 md['width'] = self.width
1169 1169 if self.height:
1170 1170 md['height'] = self.height
1171 1171 if self.unconfined:
1172 1172 md['unconfined'] = self.unconfined
1173 1173 if md:
1174 1174 return self.data, md
1175 1175 else:
1176 1176 return self.data
1177 1177
1178 1178 def _repr_png_(self):
1179 1179 if self.embed and self.format == self._FMT_PNG:
1180 1180 return self._data_and_metadata()
1181 1181
1182 1182 def _repr_jpeg_(self):
1183 1183 if self.embed and (self.format == self._FMT_JPEG or self.format == u'jpg'):
1184 1184 return self._data_and_metadata()
1185 1185
1186 1186 def _repr_gif_(self):
1187 1187 if self.embed and self.format == self._FMT_PNG:
1188 1188 return self._data_and_metadata()
1189 1189
1190 1190 def _find_ext(self, s):
1191 1191 return s.split('.')[-1].lower()
1192 1192
1193 1193 class Video(DisplayObject):
1194 1194
1195 1195 def __init__(self, data=None, url=None, filename=None, embed=False, mimetype=None):
1196 1196 """Create a video object given raw data or an URL.
1197 1197
1198 1198 When this object is returned by an input cell or passed to the
1199 1199 display function, it will result in the video being displayed
1200 1200 in the frontend.
1201 1201
1202 1202 Parameters
1203 1203 ----------
1204 1204 data : unicode, str or bytes
1205 1205 The raw video data or a URL or filename to load the data from.
1206 1206 Raw data will require passing `embed=True`.
1207 1207 url : unicode
1208 1208 A URL for the video. If you specify `url=`,
1209 1209 the image data will not be embedded.
1210 1210 filename : unicode
1211 1211 Path to a local file containing the video.
1212 1212 Will be interpreted as a local URL unless `embed=True`.
1213 1213 embed : bool
1214 1214 Should the video be embedded using a data URI (True) or be
1215 1215 loaded using a <video> tag (False).
1216 1216
1217 1217 Since videos are large, embedding them should be avoided, if possible.
1218 1218 You must confirm embedding as your intention by passing `embed=True`.
1219 1219
1220 1220 Local files can be displayed with URLs without embedding the content, via::
1221 1221
1222 1222 Video('./video.mp4')
1223 1223
1224 1224 mimetype: unicode
1225 1225 Specify the mimetype for embedded videos.
1226 1226 Default will be guessed from file extension, if available.
1227 1227
1228 1228 Examples
1229 1229 --------
1230 1230
1231 1231 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1232 1232 Video('path/to/video.mp4')
1233 1233 Video('path/to/video.mp4', embed=True)
1234 1234 Video(b'raw-videodata', embed=True)
1235 1235 """
1236 1236 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1237 1237 url = data
1238 1238 data = None
1239 1239 elif os.path.exists(data):
1240 1240 filename = data
1241 1241 data = None
1242 1242
1243 1243 if data and not embed:
1244 1244 msg = ''.join([
1245 1245 "To embed videos, you must pass embed=True ",
1246 1246 "(this may make your notebook files huge)\n",
1247 1247 "Consider passing Video(url='...')",
1248 1248 ])
1249 1249 raise ValueError(msg)
1250 1250
1251 1251 self.mimetype = mimetype
1252 1252 self.embed = embed
1253 1253 super(Video, self).__init__(data=data, url=url, filename=filename)
1254 1254
1255 1255 def _repr_html_(self):
1256 1256 # External URLs and potentially local files are not embedded into the
1257 1257 # notebook output.
1258 1258 if not self.embed:
1259 1259 url = self.url if self.url is not None else self.filename
1260 1260 output = """<video src="{0}" controls>
1261 1261 Your browser does not support the <code>video</code> element.
1262 1262 </video>""".format(url)
1263 1263 return output
1264 1264
1265 1265 # Embedded videos are base64-encoded.
1266 1266 mimetype = self.mimetype
1267 1267 if self.filename is not None:
1268 1268 if not mimetype:
1269 1269 mimetype, _ = mimetypes.guess_type(self.filename)
1270 1270
1271 1271 with open(self.filename, 'rb') as f:
1272 1272 video = f.read()
1273 1273 else:
1274 1274 video = self.data
1275 1275 if isinstance(video, str):
1276 1276 # unicode input is already b64-encoded
1277 1277 b64_video = video
1278 1278 else:
1279 1279 b64_video = base64_encode(video).decode('ascii').rstrip()
1280 1280
1281 1281 output = """<video controls>
1282 1282 <source src="data:{0};base64,{1}" type="{0}">
1283 1283 Your browser does not support the video tag.
1284 1284 </video>""".format(mimetype, b64_video)
1285 1285 return output
1286 1286
1287 1287 def reload(self):
1288 1288 # TODO
1289 1289 pass
1290 1290
1291 1291 def _repr_png_(self):
1292 1292 # TODO
1293 1293 pass
1294 1294 def _repr_jpeg_(self):
1295 1295 # TODO
1296 1296 pass
1297 1297 def _repr_gif_(self):
1298 1298 # TODO
1299 1299 pass
1300 1300
1301 1301 def clear_output(wait=False):
1302 1302 """Clear the output of the current cell receiving output.
1303 1303
1304 1304 Parameters
1305 1305 ----------
1306 1306 wait : bool [default: false]
1307 1307 Wait to clear the output until new output is available to replace it."""
1308 1308 from IPython.core.interactiveshell import InteractiveShell
1309 1309 if InteractiveShell.initialized():
1310 1310 InteractiveShell.instance().display_pub.clear_output(wait)
1311 1311 else:
1312 1312 print('\033[2K\r', end='')
1313 1313 sys.stdout.flush()
1314 1314 print('\033[2K\r', end='')
1315 1315 sys.stderr.flush()
1316 1316
1317 1317
1318 1318 @skip_doctest
1319 1319 def set_matplotlib_formats(*formats, **kwargs):
1320 1320 """Select figure formats for the inline backend. Optionally pass quality for JPEG.
1321 1321
1322 1322 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1323 1323
1324 1324 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1325 1325
1326 1326 To set this in your config files use the following::
1327 1327
1328 1328 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1329 1329 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1330 1330
1331 1331 Parameters
1332 1332 ----------
1333 1333 *formats : strs
1334 1334 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1335 1335 **kwargs :
1336 1336 Keyword args will be relayed to ``figure.canvas.print_figure``.
1337 1337 """
1338 1338 from IPython.core.interactiveshell import InteractiveShell
1339 1339 from IPython.core.pylabtools import select_figure_formats
1340 1340 # build kwargs, starting with InlineBackend config
1341 1341 kw = {}
1342 1342 from ipykernel.pylab.config import InlineBackend
1343 1343 cfg = InlineBackend.instance()
1344 1344 kw.update(cfg.print_figure_kwargs)
1345 1345 kw.update(**kwargs)
1346 1346 shell = InteractiveShell.instance()
1347 1347 select_figure_formats(shell, formats, **kw)
1348 1348
1349 1349 @skip_doctest
1350 1350 def set_matplotlib_close(close=True):
1351 1351 """Set whether the inline backend closes all figures automatically or not.
1352 1352
1353 1353 By default, the inline backend used in the IPython Notebook will close all
1354 1354 matplotlib figures automatically after each cell is run. This means that
1355 1355 plots in different cells won't interfere. Sometimes, you may want to make
1356 1356 a plot in one cell and then refine it in later cells. This can be accomplished
1357 1357 by::
1358 1358
1359 1359 In [1]: set_matplotlib_close(False)
1360 1360
1361 1361 To set this in your config files use the following::
1362 1362
1363 1363 c.InlineBackend.close_figures = False
1364 1364
1365 1365 Parameters
1366 1366 ----------
1367 1367 close : bool
1368 1368 Should all matplotlib figures be automatically closed after each cell is
1369 1369 run?
1370 1370 """
1371 1371 from ipykernel.pylab.config import InlineBackend
1372 1372 cfg = InlineBackend.instance()
1373 1373 cfg.close_figures = close
General Comments 0
You need to be logged in to leave comments. Login now