#!/usr/bin/env python # coding: utf-8 # ## For [How do I construct a python function where the input in python code and output is ipython rich output (in HTML)?](https://stackoverflow.com/q/74683522/8508004), based on [Can I capture rich outputs in plain python sessions ?](https://github.com/ipython/ipython/issues/11163#issuecomment-393338313), and [Module: utils.capture](https://ipython.readthedocs.io/en/stable/api/generated/IPython.utils.capture.html) # In[1]: pycode = ''' from matplotlib import pyplot as plt plt.plot([1, 2, 3]) plt.show() ''' from IPython.utils import io with io.capture_output() as captured: exec(pycode) # In[2]: type(captured) # Based on and [Module: utils.capture](https://ipython.readthedocs.io/en/stable/api/generated/IPython.utils.capture.html) # In[3]: captured.outputs[0] # In[4]: type(captured.outputs[0]) # In[5]: captured.show() # In[6]: captured() # In[7]: from IPython.display import display for o in captured.outputs: display(o) # ## Want a figure? # In[8]: pycode = ''' from matplotlib import pyplot as plt plt.plot([1, 2, 3]) plt.savefig('my_plot.png') plt.show() ''' from IPython.utils import io with io.capture_output() as captured: exec(pycode) # In[9]: from IPython.display import Image Image("my_plot.png") # So adding in the saving the plot as an image in the original use of matplotlib works. # # If you include a line assigning the plot to a Matplotlib figure object before you make the plot, you can also decide after-the-fact to save it as an image. # In[10]: pycode = ''' from matplotlib import pyplot as plt fig = plt.figure() plt.plot([1, 2, 3]) plt.show() ''' from IPython.utils import io with io.capture_output() as captured: exec(pycode) # In[11]: fig.savefig("after_the_fact_save.png") # In[12]: from IPython.display import Image Image("after_the_fact_save.png") # So adding in the saving the plot as an image via matplotlib works. # # But **can you save the 'RichOuput' from Matplotlib**, `captured.outputs[0]` in this case, to an image, too? So you can keep the matplotlib handling simpler and decide after-the-fact if you want the image and get it progammatically. (Because when in JupyterLab, you have the option to right-click on the plot and choose 'Create New View for Ouput' and then right-clicking on that in the 'Output View' and choosing to 'Save Image As..' to collect an image file by hand.) # Based on [here](https://github.com/ipython/ipython/issues/11163#issuecomment-393995207), I didn't think that is easy to do. And I was going to suggest digging into how [nbgather](https://github.com/microsoft/gather) works, sugged [here](https://discourse.jupyter.org/t/is-there-a-way-to-save-the-output-of-a-cell/2489/2?u=fomightez) may provide a way; howver, it didn't seem straightforward right now. # # However, I found two options based on search of 'jupyter %%capture send to image file'. First based on [a reply to 'Exporting a single jupyter cell output'](https://github.com/jupyter/notebook/issues/3039#issuecomment-583860159), where use of '%%capture to save the png output' is discussed. # # So I tried: # # ```python # with open('from_richoutputs.png', 'wb') as f: # f.write(captured.outputs[0].data['image/png']) # ``` # # However, that gives 'TypeError: a bytes-like object is required, not 'str'' because it turns out that seems written for something already outputing `.png`, I think. Even though the pseudocode doesn't work that way. # So similar with [the other promising idea](https://discourse.jupyter.org/t/cell-magic-to-save-image-output-as-a-png-file/11906/2) that turns out to be useful only when the RichOutput was a `.png`. And so I'm back to thinking not easy with matplotlib and be at least be happy one route works. (The [page with the other promising idea](https://discourse.jupyter.org/t/cell-magic-to-save-image-output-as-a-png-file/11906/2) also includes metnion of nbinteract/scrapbook and maybe that is appropriate here?) # ## Can you make the plot HTML code? # # ### nbconvert of a notebook with just that code and output to get HTML # # If you are working in Jupyter than one option to get HTML version of the plot would be to use nbconvert to make the HTML, see [here](https://nbconvert.readthedocs.io/en/latest/usage.html). Example: # # ```shell # jupyter nbconvert --to html so74683522.ipynb # ``` # # The produced HTML will contain code needed to make the plot. # You can imagine just making a notebook where the only cell is what produces the plot to make it easy to find the pertinent code. # In fact, let's demonstrate that by making a notebook that will only have the following single cell, with no output yet: # # ```python # pycode = ''' # from matplotlib import pyplot as plt # plt.plot([1, 2, 3]) # plt.show() # ''' # exec(pycode) # ``` # # To do that, I'll us nbformat to make a Jupyter notebook `.ipynb` with that code as a single cell in it by running the next cell: # In[13]: # based on https://stackoverflow.com/a/62105736/8508004 (setting metadata to python based on https://github.com/ParaToolsInc/taucmdr/blob/70daea6e775903a04d3008f4500c0ea60970e023/packages/taucmdr/analysis/analysis.py) from nbformat import v4 as nbf import nbformat code_source = "pycode = '''\nfrom matplotlib import pyplot as plt\nplt.plot([1, 2, 3])\nplt.show()\n'''\nexec(pycode)" new_ntbk = nbf.new_notebook() cells = [nbf.new_code_cell(code_source)] new_ntbk['cells'] = cells new_ntbk['metadata']['kernelspec'] = nbformat.NotebookNode() new_ntbk['metadata']['kernelspec']['name'] = 'python3' new_ntbk['metadata']['kernelspec']['language'] = 'python' new_ntbk['metadata']['kernelspec']['display_name'] = 'Python 3 (ipykernel)' nbformat.write(new_ntbk, "single_cell_exec_without_output_via_nbf.ipynb") # We can see that is all that is there: # In[14]: cat single_cell_exec_without_output_via_nbf.ipynb # That's it. Just contains that small block of code we made above. Let's excute it # In[15]: # based on https://nbconvert.readthedocs.io/en/latest/execute_api.html#executing-notebooks-using-the-python-api-interface notebook_filename = "single_cell_exec_without_output_via_nbf.ipynb" import nbformat from nbconvert.preprocessors import ExecutePreprocessor with open(notebook_filename) as f: nb = nbformat.read(f, as_version=4) ExecutePreprocessor().preprocess(nb, {'metadata': {'path': './'}}) with open('executed_notebook.ipynb', 'w', encoding='utf-8') as f: nbformat.write(nb, f) # That will execute the notebook. Now to get the HTML we'll convert it and specify we don't want the input cell source in the result, see [here](https://stackoverflow.com/a/58524517/8508004) for more about the `--no-input` flag to leave out the code cells from the produced HTML. # In[16]: get_ipython().system('jupyter nbconvert --no-input --to html executed_notebook.ipynb') # Let's view the first few lines of HTML code produced. # In[17]: get_ipython().system('head executed_notebook.html') # Replace `!head` part of the command above with `cat` and re-run it to see it all. There's a lot and so I just wanted to show the top to demonstrate it indeed produced an HTML file. You should be able to take the code and embed it elsewhere. In fact, if you really examine it you'll see it has an image of the plot embedded as base64. You can deduce how to embed only that elsewhere from examples below. # # To view the HTML as HTML, in JupyterLab you can just double-click on the HTML icon in the file browser pane to the left. You'll note there is just a plot. # # #### Make a image of the plot after-the-fact and converting that to HTML # # Also, using the figure, you can create a Base64 version of the plot image to embed in HTML, see [here](https://stackoverflow.com/a/51533307/8508004). # In fact let's see that demonstrated in this notebook, based on a variation [here](https://stackoverflow.com/a/36993713/8508004), in reply to [Imbed [sic] matplotlib figure into iPython HTML](https://stackoverflow.com/q/36991600/8508004). Plus, updating that to Python 3 using [here](https://discourse.matplotlib.org/t/savefig-and-stringio-error-on-python3/18894/3) and [here](https://stackoverflow.com/a/62498083/8508004). # In[18]: from IPython.core.display import HTML import binascii #from io import StringIO from io import BytesIO import base64 import matplotlib.pyplot as plt # open IO object #sio = StringIO() sio = BytesIO() pycode = ''' from matplotlib import pyplot as plt fig = plt.figure() plt.plot([1, 2, 3]) plt.show() ''' from IPython.utils import io with io.capture_output() as captured: exec(pycode) # print raw canvas data to IO object #fig.canvas.print_png(sio) fig.savefig(sio, format="png") # convert raw binary data to base64 # I use this to embed in an img tag #img_data = binascii.b2a_base64(sio.getvalue()) img_data = base64.encodebytes(sio.getvalue()).decode() # keep img tag outter html in its own variable img_html = ''.format(img_data) HTML("

Hello, this is all HTML in this output block:


"+img_html+"
") # Making a figure object, the `fig = plt.figure()` step, was introduced in the section above. # # #### Use the Matplotlib figure object to go to embeddable HTML # # However, the OP was seeking to be away from Jupyter and IPython. Can you make HTML from matplotlib objects? # # Using [mpld3](https://mpld3.github.io/quickstart.html) you can. That package has an `fig_to_html()` method. # Let's install that here: # In[19]: get_ipython().run_line_magic('pip', 'install mpld3') # Let's connect mpld3 use to this process to make an HTML version of the plot: # In[20]: pycode = ''' from matplotlib import pyplot as plt fig = plt.figure() plt.plot([1, 2, 3]) plt.show() ''' from IPython.utils import io with io.capture_output() as captured: exec(pycode) import mpld3 html_string= mpld3.fig_to_html(fig) # How to make a figure object was covered in the section above. Besides, using that, we've just import `mpl` and used the `fig_to_html()` method using the Matplotlib figure object. # # That should have produced HTML; let's examine it to see: # In[21]: print(html_string) # Does it work? Because I'm demonstrating this in a Jupyter notebook and it has the ability to render HTML, I should be able to see. # In[22]: from IPython.core.display import HTML HTML(html_string) # The D3 version shows the same infomation as the original plot. The aesthetics are slightly different. Importantly, it is HTML generated via Python that the OP wanted with not too much addition or change to the original code block posted. # # Interestingly, the D3 plots have additional abilities you can access in the bottom right corner so that these are interactive. In fact, as highlighted [in this notebook here](https://nbviewer.org/gist/aflaxman/8780140), which I learned of [here](https://stackoverflow.com/q/36085869/8508004), you can use additional plugins from mpld3, to set the zoom settings at the outset and add a reset button.