ricklupton / floweaver

View flow data as Sankey diagrams
https://floweaver.readthedocs.io
MIT License
444 stars 89 forks source link

How to combine Floweaver with interactive widgets? #67

Open ralienpp opened 5 years ago

ralienpp commented 5 years ago

I am building an interactive system that uses Jupyter widgets to control the settings that go into the Sankey diagram definition. The idea is to render the diagram when a button is pressed, at this stage I check the custom values entered by the user and adjust the diagram parameters accordingly.

I have the following proof of concept code, which is just a minor extension of the example from the tutorial, it doesn't have any diagram tweaking controls, but it is tied to the button that should trigger the rendering:

import io
import pandas as pd
from ipysankeywidget import SankeyWidget
from ipywidgets import widgets
from IPython.display import display

raw = b'''source,target,type,value
farm1,Mary,apples,5
farm1,James,apples,3
farm2,Fred,apples,10
farm2,Fred,bananas,10
farm2,Susan,bananas,5
farm3,Susan,apples,10
farm4,Susan,bananas,1
farm5,Susan,bananas,1
farm6,Susan,bananas,1'''
flows = pd.read_csv(io.BytesIO(raw))

render = widgets.Button(description="Render diagram")
display(render)

def on_button_clicked(b):
    SankeyWidget(links=flows.to_dict('records'))
    print('Rendering done')

render.on_click(on_button_clicked)

It displays the button, and when I click it, the console says "Rendering done", but the diagram is not shown. However, if I take this line SankeyWidget(links=flows.to_dict('records')) into a separate cell, the diagram is rendered:

image

As I am no expert in Jupyter nor Floweaver, my guess is that only one entity can decide what is drawn in the cell - and since the button widget is the first one that's shown, it takes over the entire cell output. I would like to know

  1. Whether what I try to accomplish is possible, in principle.
  2. If there are any cookbock recipes I could try, to get the desired effect.

Thanks!

ricklupton commented 5 years ago

I'd use a VBox widget. You can create the button and SankeyWidget once and display them (by putting them at the end of a cell, or calling display), then update the SankeyWidget attributes.

Note that widget.links = ... will automatically update, but if you edit the properties of an individual link object directly the widget has no way of noticing the change, so you have to call widget.send_state() as in the ipysankeywidget example notebooks.

ralienpp commented 5 years ago

Thank you for the answer. For the benefit of future archaeologists, here is the working solution

import io
import pandas as pd
from ipysankeywidget import SankeyWidget
from ipywidgets import widgets
from IPython.display import display, clear_output

raw = b'''source,target,type,value
farm1,Mary,apples,5
farm1,James,apples,3
farm2,Fred,apples,10
farm2,Fred,bananas,10
farm2,Susan,bananas,5
farm3,Susan,apples,10
farm4,Susan,bananas,1
farm5,Susan,bananas,1
farm6,Susan,bananas,1'''
flows = pd.read_csv(io.BytesIO(raw))

render = widgets.Button(description="Render diagram")
display(render)

def on_button_clicked(b):
    diagram = SankeyWidget(links=flows.to_dict('records'))
    print('Rendering done')
    clear_output( )
    display(render, diagram)

render.on_click(on_button_clicked)

The clear_output() call redraws the whole thing, without it, new diagrams would be appended to the bottom of the cell every time you make a click.

My guess is that it might be useful to add a section to the manual, that provides this basic example of interactive diagrams, and maybe something beyond it too; since I doubt that I am the only person on the planet who bumped into this use case :-). If you're OK with this, I can make a pull request with an initial version.

ricklupton commented 5 years ago

Glad you figured it out! More examples would be great 👍 Maybe add it to the cookbook section of the docs?