#!/usr/bin/env python # coding: utf-8 # # Markdown in ipywidget widget guide 2024 # # (Worked out for an aspect of SO answer https://stackoverflow.com/a/78674203/8508004.) # # The proposed example, shown below only required proper rendering of a couple of features of markdown: # # ```python # #based on https://stackoverflow.com/q/78670150/8508004, and adapting to put matplotlib in a widget from https://stackoverflow.com/a/51060721/8508004 (which I think was adapted in some ways from https://github.com/jupyter-widgets/ipywidgets/issues/2821) # from ipywidgets import widgets, Layout # from IPython.display import display, Javascript, Markdown as md # import numpy as np # import matplotlib.pyplot as plt # # left_widget = widgets.Output() # def func(x, a, b, c, d): # return a + b*np.exp(c*x + d) # # the_x = np.arange(0, 10, 0.5) # the_y = func(the_x, 1, 1, -1, 2) # # with left_widget: # fig, ax = plt.subplots() # ax.plot(the_x, the_y) # plt.show(fig) # # len_x = len(the_x) # # mdout = md(f"""There are {len_x} **elements** of both `the_x`, and `the_y`; # Those values are plotted on the diagram on the left""") # # box_layout = Layout(display='flex', # flex_flow='row', # justify_content='space-around', # width='auto' # ) # # hbox1 = widgets.Box(children=[left_widget, widgets.HTML(mdout._repr_markdown_())], layout=box_layout) # display(hbox1) # ``` # Namely, bold items flanked by double asterisks and highlight as code text flanked by a backtick. # However, because bold indication by double asterisks seems problematic, I added a third example to better illustrate some of markdown rendering is handled adequetely without special measures. # The third example added is section heading handling. # # That gives as the real starting point for much of what is covered here: # # ```python # #based on https://stackoverflow.com/q/78670150/8508004, and adapting to put matplotlib in a widget from https://stackoverflow.com/a/51060721/8508004 (which I think was adapted in some ways from https://github.com/jupyter-widgets/ipywidgets/issues/2821) # from ipywidgets import widgets, Layout # from IPython.display import display, Javascript, Markdown as md # import numpy as np # import matplotlib.pyplot as plt # # left_widget = widgets.Output() # def func(x, a, b, c, d): # return a + b*np.exp(c*x + d) # # the_x = np.arange(0, 10, 0.5) # the_y = func(the_x, 1, 1, -1, 2) # # with left_widget: # fig, ax = plt.subplots() # ax.plot(the_x, the_y) # plt.show(fig) # # len_x = len(the_x) # # md_content = f"""# (Markdown Heading Rendered) RESULTS: # # There are {len_x} **elements** of both `the_x`, and `the_y`; # Those values are plotted on the diagram on the left""" # # box_layout = Layout(display='flex', # flex_flow='row', # justify_content='space-around', # width='auto' # ) # # hbox1 = widgets.Box(children=[left_widget, widgets.HTML(mdout._repr_markdown_())], layout=box_layout) # display(hbox1) # ``` # # ------ # # ## Proposed solution for post: # In[4]: #based on https://stackoverflow.com/q/78670150/8508004, and adapting to put matplotlib in a widget from https://stackoverflow.com/a/51060721/8508004 (which I think was adapted in some ways from https://github.com/jupyter-widgets/ipywidgets/issues/2821) from ipywidgets import widgets, Layout, HTML from IPython.display import display import numpy as np import matplotlib.pyplot as plt import markdown left_widget = widgets.Output() def func(x, a, b, c, d): return a + b*np.exp(c*x + d) the_x = np.arange(0, 10, 0.5) the_y = func(the_x, 1, 1, -1, 2) with left_widget: fig, ax = plt.subplots() ax.plot(the_x, the_y) plt.show(fig) len_x = len(the_x) md_content = f"""# (Markdown Heading Rendered) RESULTS: There are {len_x} **elements** of both `the_x`, and `the_y`; Those values are plotted on the diagram on the left""" box_layout = Layout(display='flex', flex_flow='row', justify_content='space-around', width='auto' ) html_content = markdown.markdown(md_content) markdown_widget = widgets.HTML( value=f"""
{html_content}
""" ) hbox1 = widgets.Box(children=[left_widget, markdown_widget], layout=box_layout) display(hbox1) # How did I get there? # # That is covered below step-by-step, but here are some alternatives. The step-by-step will cover those, too. And why simpler adaptations may be all you need. # # ### Alternative solution # # **with `markdown2`** # In[6]: #based on https://stackoverflow.com/q/78670150/8508004, and adapting to put matplotlib in a widget from https://stackoverflow.com/a/51060721/8508004 (which I think was adapted in some ways from https://github.com/jupyter-widgets/ipywidgets/issues/2821) from ipywidgets import widgets, Layout import numpy as np import matplotlib.pyplot as plt import markdown2 left_widget = widgets.Output() def func(x, a, b, c, d): return a + b*np.exp(c*x + d) the_x = np.arange(0, 10, 0.5) the_y = func(the_x, 1, 1, -1, 2) with left_widget: fig, ax = plt.subplots() ax.plot(the_x, the_y) plt.show(fig) len_x = len(the_x) md_content = f"""# (Markdown Heading Rendered) RESULTS: There are {len_x} **elements** of both `the_x`, and `the_y`; Those values are plotted on the diagram on the left""" box_layout = Layout(display='flex', flex_flow='row', justify_content='space-around', width='auto' ) html_content = markdown2.markdown(md_content) markdown_widget = widgets.HTML( value=f"""
{html_content}
""" ) hbox1 = widgets.Box(children=[left_widget, markdown_widget], layout=box_layout) display(hbox1) # There's actually **a third alternative** presented at the bottom of this document suggesting converting the markdown feature calls to HTML directly and using CSS for full-featured control of the styling. # # -------- # # ### `markdown2` use # # Note these **SEPARATE** solutions need to be tested in separate **fresh** sessions so that the CSS used isn't working behind-the-scenes and misleading as to what is working or not. # # Starting with our starting point that has section heading, we'll drop the line `from IPython.display import display, Javascript, Markdown as md` and add in `markdown2` use to convert Markdown syntax into HTML. (This happened to be where I started but it turns out `markdown` is fine for this, it seems, too. See below.) This conversion is necessary because the markdown text inside an ipywidget HTML widget doesn't get converted automatically like markdown does in a markdown cell in Jupyter, a markdown cell being able to handle markdown and HTML out of the box. # You'll most likely need to install that markdown2 package to use it. Install it from within the running Juyter notebooks with either `%conda install -c conda-forge markdown2` or `%pip install markdown2`. # # In[7]: #based on https://stackoverflow.com/q/78670150/8508004, and adapting to put matplotlib in a widget from https://stackoverflow.com/a/51060721/8508004 (which I think was adapted in some ways from https://github.com/jupyter-widgets/ipywidgets/issues/2821) from ipywidgets import widgets, Layout import numpy as np import matplotlib.pyplot as plt import markdown2 left_widget = widgets.Output() def func(x, a, b, c, d): return a + b*np.exp(c*x + d) the_x = np.arange(0, 10, 0.5) the_y = func(the_x, 1, 1, -1, 2) with left_widget: fig, ax = plt.subplots() ax.plot(the_x, the_y) plt.show(fig) len_x = len(the_x) md_content = f"""# (Markdown Heading Rendered) RESULTS: There are {len_x} **elements** of both `the_x`, and `the_y`; Those values are plotted on the diagram on the left""" box_layout = Layout(display='flex', flex_flow='row', justify_content='space-around', width='auto' ) html_content = markdown2.markdown(md_content) hbox1 = widgets.Box(children=[left_widget, widgets.HTML(html_content)], layout=box_layout) display(hbox1) # That use of `markdown2` to convert the string with the content to put in the HTML widget gets us most of the way there. # However, the code highlighting in between the ticks isn't rendered. I don't know why that specification isn't handled right, but a workaround that works is is to add the styling to the HTML with custom CSS styling and then that feature also renders correctly. This is because the HTML widget doesn't automatically inherit all the default styles that would normally be applied to HTML in a Jupyter markdown cell. # In[8]: #based on https://stackoverflow.com/q/78670150/8508004, and adapting to put matplotlib in a widget from https://stackoverflow.com/a/51060721/8508004 (which I think was adapted in some ways from https://github.com/jupyter-widgets/ipywidgets/issues/2821) from ipywidgets import widgets, Layout, HTML import numpy as np import matplotlib.pyplot as plt import markdown2 left_widget = widgets.Output() def func(x, a, b, c, d): return a + b*np.exp(c*x + d) the_x = np.arange(0, 10, 0.5) the_y = func(the_x, 1, 1, -1, 2) with left_widget: fig, ax = plt.subplots() ax.plot(the_x, the_y) plt.show(fig) len_x = len(the_x) md_content = f"""# (Markdown Heading Rendered) RESULTS: There are {len_x} **elements** of both `the_x`, and `the_y`; Those values are plotted on the diagram on the left""" box_layout = Layout(display='flex', flex_flow='row', justify_content='space-around', width='auto' ) html_content = markdown2.markdown(md_content) markdown_widget = HTML( value=f"""
{html_content}
""" ) hbox1 = widgets.Box(children=[left_widget, markdown_widget], layout=box_layout) display(hbox1) # There everything is rendered as markdown well in the right side of the output. # # **Note that if I didn't care about using ticks to indicate code highlighting, I pretty much could have used `markdown2` to convert the markdown text to HTML and been done.** I wouldn't have needed to wrap the HTML content in CSS control if that had been the case. # # ------ # # ### `markdown` can work as well # # Note these **SEPARATE** solutions need to be tested in separate **fresh** sessions so that the CSS used isn't working behind-the-scenes and misleading as to what is working or not. # # `markdown` can work, too, for the conversion of Markdown syntax into HTML. # # We'll go back to the starting point from the top of this document and add in `markdown` for the conversion of the text to HTML. # # In[1]: #based on https://stackoverflow.com/q/78670150/8508004, and adapting to put matplotlib in a widget from https://stackoverflow.com/a/51060721/8508004 (which I think was adapted in some ways from https://github.com/jupyter-widgets/ipywidgets/issues/2821) from ipywidgets import widgets, Layout import numpy as np import matplotlib.pyplot as plt import markdown left_widget = widgets.Output() def func(x, a, b, c, d): return a + b*np.exp(c*x + d) the_x = np.arange(0, 10, 0.5) the_y = func(the_x, 1, 1, -1, 2) with left_widget: fig, ax = plt.subplots() ax.plot(the_x, the_y) plt.show(fig) len_x = len(the_x) md_content = f"""# (Markdown Heading Rendered) RESULTS: There are {len_x} **elements** of both `the_x`, and `the_y`; Those values are plotted on the diagram on the left""" box_layout = Layout(display='flex', flex_flow='row', justify_content='space-around', width='auto' ) html_content = markdown.markdown(md_content) hbox1 = widgets.Box(children=[left_widget, widgets.HTML(html_content)], layout=box_layout) display(hbox1) # Just as above, the use of `markdown` to convert the string with the content to put in the HTML widget gets us most of the way there. # However, the code in between the ticks isn't getting rendered as 'code' styling. Again, we can use the workaround of adding the CSS styling to wrap the HTML and then that feature also renders correctly. # In[2]: #based on https://stackoverflow.com/q/78670150/8508004, and adapting to put matplotlib in a widget from https://stackoverflow.com/a/51060721/8508004 (which I think was adapted in some ways from https://github.com/jupyter-widgets/ipywidgets/issues/2821) from ipywidgets import widgets, Layout, HTML import numpy as np import matplotlib.pyplot as plt import markdown left_widget = widgets.Output() def func(x, a, b, c, d): return a + b*np.exp(c*x + d) the_x = np.arange(0, 10, 0.5) the_y = func(the_x, 1, 1, -1, 2) with left_widget: fig, ax = plt.subplots() ax.plot(the_x, the_y) plt.show(fig) len_x = len(the_x) md_content = f"""# (Markdown Heading Rendered) RESULTS: There are {len_x} **elements** of both `the_x`, and `the_y`; Those values are plotted on the diagram on the left""" box_layout = Layout(display='flex', flex_flow='row', justify_content='space-around', width='auto' ) html_content = markdown.markdown(md_content) markdown_widget = widgets.HTML( value=f"""
{html_content}
""" ) html_content = markdown.markdown(md_content) hbox1 = widgets.Box(children=[left_widget, markdown_widget], layout=box_layout) display(hbox1) # As with above: # **Note that if I didn't care about using double asterisks to indicate bold, I pretty much could have used `markdown` to convert the markdown text to HTML and been done.** I wouldn't have needed to wrap the HTML content in CSS control if that had been the case. # # -------- # # # #### Full control via HTML tag substitution & CSS # # Finally, if we go back to considering the original code with no section headings involved, you can have full control over all elements with using HTML & CSS for all. This type of approach can allow fine grain control if `markdown` or `markdown2` isn't covering it. # In[9]: from ipywidgets import widgets, Layout #from IPython.display import display, Javascript, Markdown as md import numpy as np import matplotlib.pyplot as plt left_widget = widgets.Output() def func(x, a, b, c, d): return a + b*np.exp(c*x + d) the_x = np.arange(0, 10, 0.5) the_y = func(the_x, 1, 1, -1, 2) with left_widget: fig, ax = plt.subplots() ax.plot(the_x, the_y) plt.show(fig) len_x = len(the_x) md_content = f"""There are {len_x} **elements** of both `the_x`, and `the_y`; Those values are plotted on the diagram on the left""" box_layout = Layout(display='flex', flex_flow='row', justify_content='space-around', width='auto' ) def simple_markdown_to_html(text): # Handle bold text text = text.replace('**', '', 1).replace('**', '', 1) # Handle inline code while '`' in text: text = text.replace('`', '', 1).replace('`', '', 1) return text html_content = simple_markdown_to_html(md_content) markdown_widget = widgets.HTML( value=f"""
{html_content}
""" ) hbox1 = widgets.Box(children=[left_widget, markdown_widget], layout=box_layout) display(hbox1)