%22%22%22Live%20Loman%20computation%20widget%20demonstration.%22%22%22%0A%0A%23%20%2F%2F%2F%20script%0A%23%20requires-python%20%3D%20%22%3E%3D3.11%22%0A%23%20dependencies%20%3D%20%5B%0A%23%20%20%20%20%20%22marimo%3D%3D0.23.13%22%2C%0A%23%20%20%20%20%20%22loman%5Bui%5D%22%2C%0A%23%20%5D%0A%23%0A%23%20%5Btool.uv.sources%5D%0A%23%20loman%20%3D%20%7B%20path%20%3D%20%22..%2F..%22%2C%20editable%20%3D%20true%20%7D%0A%23%20%2F%2F%2F%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.23.13%22%0Aapp%20%3D%20marimo.App(width%3D%22full%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20A%20live%20computation%20graph%0A%0A%20%20%20%20%60loman%5Bui%5D%60%20turns%20a%20%60Computation%60%20into%20a%20notebook-native%20dashboard.%20The%20graph%0A%20%20%20%20is%20laid%20out%20by%20Loman's%20existing%20Graphviz%20renderer%2C%20so%20it%20is%20the%20same%20picture%0A%20%20%20%20%60comp.draw()%60%20gives%20you%20%E2%80%94%20but%20selection%2C%20node%20details%2C%20state%20changes%20and%0A%20%20%20%20lightweight%20controls%20are%20live.%0A%0A%20%20%20%20The%20widget%20follows%20its%20computation%20through%20%60Computation.subscribe()%60.%20It%20is%0A%20%20%20%20never%20recreated%2C%20and%20no%20separate%20web%20server%20is%20running.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%0A%20%20%20%20import%20loman%0A%0A%20%20%20%20return%20loman%2C%20mo%2C%20np%2C%20pd%0A%0A%0A%40app.cell%0Adef%20_(loman%2C%20np%2C%20pd)%3A%0A%20%20%20%20def%20build_book()%3A%0A%20%20%20%20%20%20%20%20%22%22%22A%20small%20book%20holding%20every%20value%20type%20the%20widget%20can%20render.%22%22%22%0A%20%20%20%20%20%20%20%20computation%20%3D%20loman.Computation(metadata%3D%7B%22purpose%22%3A%20%22widget%20feature%20tour%22%7D)%0A%0A%20%20%20%20%20%20%20%20%23%20Scalars%2C%20editable%20directly%20in%20the%20detail%20panel.%0A%20%20%20%20%20%20%20%20computation.add_node(%22market%2Fspot%22%2C%20value%3D100.0)%0A%20%20%20%20%20%20%20%20computation.add_node(%22position%2Fquantity%22%2C%20value%3D12)%0A%20%20%20%20%20%20%20%20computation.add_node(%22risk%2Fshock%22%2C%20value%3D-0.08)%0A%0A%20%20%20%20%20%20%20%20%23%20A%20DataFrame%2C%20editable%20cell%20by%20cell.%0A%20%20%20%20%20%20%20%20computation.add_node(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22market%2Fholdings%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20value%3Dpd.DataFrame(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ticker%22%3A%20%5B%22AAPL%22%2C%20%22MSFT%22%2C%20%22NVDA%22%2C%20%22TSLA%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22weight%22%3A%20%5B0.4%2C%200.3%2C%200.2%2C%200.1%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22shares%22%3A%20%5B1200%2C%20800%2C%20450%2C%20300%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22active%22%3A%20%5BTrue%2C%20True%2C%20False%2C%20True%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20A%20Series%2C%20also%20cell-editable.%0A%20%20%20%20%20%20%20%20computation.add_node(%22market%2Ftenors%22%2C%20value%3Dpd.Series(%5B0.5%2C%201.0%2C%202.0%2C%205.0%2C%2010.0%5D%2C%20name%3D%22years%22))%0A%0A%20%20%20%20%20%20%20%20%23%20An%20array%2C%20rendered%20as%20a%20table%20but%20deliberately%20read-only.%0A%20%20%20%20%20%20%20%20computation.add_node(%22market%2Fcorr%22%2C%20value%3Dnp.array(%5B%5B1.0%2C%200.35%5D%2C%20%5B0.35%2C%201.0%5D%5D))%0A%0A%20%20%20%20%20%20%20%20%23%20Nested%20configuration%2C%20rendered%20as%20a%20tree.%0A%20%20%20%20%20%20%20%20computation.add_node(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22market%2Fconfig%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20value%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22engine%22%3A%20%7B%22seed%22%3A%2042%2C%20%22paths%22%3A%2010_000%2C%20%22antithetic%22%3A%20True%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22limits%22%3A%20%7B%22var%22%3A%205.0e6%2C%20%22dv01%22%3A%2025_000%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22desks%22%3A%20%5B%22rates%22%2C%20%22credit%22%2C%20%22equity%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20computation.add_node(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22position%2Fmarket_value%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20lambda%20spot%2C%20quantity%3A%20spot%20*%20quantity%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20kwds%3D%7B%22spot%22%3A%20%22market%2Fspot%22%2C%20%22quantity%22%3A%20%22position%2Fquantity%22%7D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20computation.add_node(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22position%2Fweighted%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20lambda%20holdings%2C%20mv%3A%20float((holdings%5B%22weight%22%5D%20*%20mv).sum())%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20kwds%3D%7B%22holdings%22%3A%20%22market%2Fholdings%22%2C%20%22mv%22%3A%20%22position%2Fmarket_value%22%7D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20computation.add_node(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22risk%2Fstressed_value%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20lambda%20mv%2C%20shock%3A%20mv%20*%20(1%20%2B%20shock)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20kwds%3D%7B%22mv%22%3A%20%22position%2Fmarket_value%22%2C%20%22shock%22%3A%20%22risk%2Fshock%22%7D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20computation.add_node(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22risk%2Fheadroom%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20lambda%20config%3A%20config%5B%22limits%22%5D%5B%22var%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20kwds%3D%7B%22config%22%3A%20%22market%2Fconfig%22%7D%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20A%20node%20that%20fails%2C%20so%20the%20traceback%20panel%20has%20something%20to%20show.%0A%20%20%20%20%20%20%20%20computation.add_node(%22risk%2Fzero_budget%22%2C%20lambda%3A%200.0)%0A%20%20%20%20%20%20%20%20computation.add_node(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22risk%2Futilisation%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20lambda%20used%2C%20budget%3A%20used%20%2F%20budget%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20kwds%3D%7B%22used%22%3A%20%22risk%2Fstressed_value%22%2C%20%22budget%22%3A%20%22risk%2Fzero_budget%22%7D%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20computation.add_node(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22report%2Fstress_loss%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20lambda%20base%2C%20stressed%3A%20stressed%20-%20base%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20kwds%3D%7B%22base%22%3A%20%22position%2Fmarket_value%22%2C%20%22stressed%22%3A%20%22risk%2Fstressed_value%22%7D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20computation.compute_all()%0A%20%20%20%20%20%20%20%20return%20computation%0A%0A%20%20%20%20book%20%3D%20build_book()%0A%20%20%20%20return%20(book%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20The%20widget%0A%0A%20%20%20%20Blocks%20start%20collapsed%2C%20so%20a%20large%20graph%20opens%20as%20a%20readable%20overview.%20Try%3A%0A%0A%20%20%20%20%7C%20Action%20%7C%20How%20%7C%0A%20%20%20%20%7C---%7C---%7C%0A%20%20%20%20%7C%20Open%20a%20block%20in%20place%20%7C%20Click%20it%20%7C%0A%20%20%20%20%7C%20Isolate%20a%20block%20%7C%20**Alt-click**%20it%20%7C%0A%20%20%20%20%7C%20Come%20back%20out%20%7C%20The%20**breadcrumb**%20under%20the%20toolbar%20%7C%0A%20%20%20%20%7C%20Close%20an%20open%20block%20%7C%20Click%20its%20**title**%20%7C%0A%20%20%20%20%7C%20Close%20everything%20%7C%20**Collapse%20all**%20%7C%0A%20%20%20%20%7C%20Inspect%20a%20node%20%7C%20Click%20it%20%E2%80%94%20**Escape**%20closes%20the%20panel%20%7C%0A%20%20%20%20%7C%20Graph%20direction%20%7C%20**LR**%20%2F%20**TB**%20toggle%20%7C%0A%20%20%20%20%7C%20Zoom%20%7C%20**%2B**%20%2F%20**%E2%88%92**%2C%20or%20ctrl%2F%E2%8C%98%20with%20the%20wheel%20%7C%0A%20%20%20%20%7C%20Whole%20graph%20%7C%20**Fit**%20%E2%80%94%20**1%3A1**%20returns%20to%20natural%20size%20%7C%0A%20%20%20%20%7C%20Pan%20%7C%20Drag%20the%20background%20%7C%0A%0A%20%20%20%20%60risk%60%20is%20red%20because%20%60risk%2Futilisation%60%20divides%20by%20zero.%20Open%20the%20block%2C%0A%20%20%20%20click%20the%20failing%20node%2C%20and%20the%20panel%20shows%20the%20traceback.%20The%20panel%20only%0A%20%20%20%20appears%20once%20you%20ask%20for%20it%2C%20so%20until%20then%20the%20graph%20has%20the%20full%20width.%0A%0A%20%20%20%20The%20graph%20has%20no%20page%20of%20its%20own%20%E2%80%94%20Graphviz%20is%20told%20to%20paint%20none%20%E2%80%94%20so%20it%0A%20%20%20%20sits%20directly%20on%20marimo's%20background%20and%20follows%20the%20notebook's%20theme.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(book%2C%20mo)%3A%0A%20%20%20%20widget%20%3D%20book.widget()%0A%20%20%20%20widget_ui%20%3D%20mo.ui.anywidget(widget)%0A%20%20%20%20return%20widget%2C%20widget_ui%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20widget_ui)%3A%0A%20%20%20%20%23%20Marimo's%20static%20exporter%20rejects%20inlined%20%60data%3A%60%20JavaScript%2C%20so%20an%20exported%0A%20%20%20%20%23%20page%20falls%20back%20to%20the%20rendered%20SVG.%20A%20live%20kernel%20gets%20the%20real%20widget.%0A%20%20%20%20_static_export%20%3D%20%22data%3Atext%2Fjavascript%22%20in%20widget_ui.text%0A%20%20%20%20mo.Html(widget_ui.widget.graph_svg)%20if%20_static_export%20else%20widget_ui%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Value%20types%0A%0A%20%20%20%20Open%20%60market%60%20and%20click%20through%20its%20nodes.%20The%20detail%20panel%20renders%20each%0A%20%20%20%20according%20to%20what%20it%20holds%3A%0A%0A%20%20%20%20%7C%20Node%20%7C%20Value%20%7C%20Shown%20as%20%7C%20Editable%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20%60market%2Fspot%60%20%7C%20%60float%60%20%7C%20A%20field%20%7C%20Yes%20%7C%0A%20%20%20%20%7C%20%60market%2Fholdings%60%20%7C%20%60DataFrame%60%20%7C%20A%20table%20%7C%20Yes%2C%20per%20cell%20%7C%0A%20%20%20%20%7C%20%60market%2Ftenors%60%20%7C%20%60Series%60%20%7C%20A%20table%20%7C%20Yes%2C%20per%20cell%20%7C%0A%20%20%20%20%7C%20%60market%2Fcorr%60%20%7C%20%60ndarray%60%20%7C%20A%20table%20%7C%20No%20%7C%0A%20%20%20%20%7C%20%60market%2Fconfig%60%20%7C%20%60dict%60%20%7C%20A%20tree%20%7C%20No%20%7C%0A%0A%20%20%20%20Click%20a%20table%20cell%20to%20edit%20it%3A%20Enter%20commits%2C%20Escape%20cancels.%20A%20cell%20edit%20is%0A%20%20%20%20an%20ordinary%20%60comp.insert%60%20of%20a%20**modified%20copy**%2C%20so%20downstream%20nodes%20go%0A%20%20%20%20stale%20exactly%20as%20they%20would%20from%20Python%2C%20and%20column%20dtypes%20are%20enforced%0A%20%20%20%20rather%20than%20coerced.%20Arrays%20stay%20read-only%20because%20NumPy%20coerces%20silently%20on%0A%20%20%20%20assignment%2C%20so%20an%20edit%20could%20change%20a%20value%20without%20saying%20so.%0A%0A%20%20%20%20Large%20values%20are%20never%20sent%20whole%3A%20a%20table%20shows%20its%20**last**%2050%20rows%20and%0A%20%20%20%20first%2020%20columns%20and%20says%20so.%20Rows%20are%20usually%20appended%2C%20so%20the%20recent%20end%0A%20%20%20%20is%20the%20interesting%20one.%0A%0A%20%20%20%20For%20the%20whole%20thing%2C%20press%20**Show%20full**.%20The%20widget%20cannot%20render%20it%20%E2%80%94%20it%0A%20%20%20%20is%20host-neutral%20and%20calling%20marimo's%20renderers%20would%20make%20the%20extra%20depend%0A%20%20%20%20on%20marimo%20and%20break%20Jupyter%20%E2%80%94%20so%20it%20publishes%20the%20node%20name%20and%20the%20cell%0A%20%20%20%20below%20renders%20it%20with%20marimo's%20own%20table.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20widget%2C%20widget_ui)%3A%0A%20%20%20%20%23%20Reading%20the%20wrapper's%20value%20makes%20this%20cell%20react%20to%20the%20Show%20full%20button.%0A%20%20%20%20_widget_state%20%3D%20widget_ui.value%0A%20%20%20%20_name%20%3D%20widget.full_view%0A%20%20%20%20if%20not%20_name%3A%0A%20%20%20%20%20%20%20%20full_view_panel%20%3D%20mo.md(%22**Nothing%20opened.**%20Press%20**Show%20full**%20on%20a%20node's%20value%20above.%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20full_view_panel%20%3D%20mo.vstack(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22%23%23%23%20%60%7B_name%7D%60%20in%20full%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.ui.table(widget.full_view_value%2C%20selection%3DNone)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20hasattr(widget.full_view_value%2C%20%22shape%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20mo.json(widget.full_view_value)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20full_view_panel%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20The%20selection%20is%20real%20Python%0A%0A%20%20%20%20**The%20widget%20navigates%20and%20lightly%20controls%3B%20the%20real%20object%20stays%20in%0A%20%20%20%20Python.**%20That%20is%20what%20makes%20it%20a%20dashboard%20component%20rather%20than%20a%20walled%0A%20%20%20%20garden.%20Click%20any%20node%20and%20this%20panel%20follows.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(book%2C%20mo%2C%20widget%2C%20widget_ui)%3A%0A%20%20%20%20%23%20Reading%20the%20marimo%20wrapper's%20value%20makes%20this%20cell%20reactive%20to%20selection.%0A%20%20%20%20_widget_state%20%3D%20widget_ui.value%0A%20%20%20%20_selected%20%3D%20widget.selected_name%0A%20%20%20%20if%20_selected%20is%20None%3A%0A%20%20%20%20%20%20%20%20selection_panel%20%3D%20mo.md(%22**Nothing%20selected.**%20Click%20a%20node%20in%20the%20graph%20above.%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20selection_panel%20%3D%20mo.vstack(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22%60widget.selected_name%60%20%E2%86%92%20%60%7B_selected!r%7D%60%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22%60comp.s%5B...%5D%60%20%E2%86%92%20%60%7Bbook.state(_selected)%7D%60%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22%60comp.i%5B...%5D%60%20%E2%86%92%20%60%7Bbook.get_inputs(_selected)%7D%60%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20selection_panel%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Following%20the%20computation%0A%0A%20%20%20%20The%20widget%20subscribes%20to%20the%20computation%2C%20so%20it%20repaints%20whether%20a%20change%0A%20%20%20%20comes%20from%20the%20browser%20or%20from%20Python.%20Move%20a%20slider%3A%20the%20graph%20is%20mutated%0A%20%20%20%20through%20the%20ordinary%20API%20and%20the%20widget%20follows.%0A%0A%20%20%20%20State-only%20changes%20repaint%20the%20existing%20picture%20in%20place%20%E2%80%94%20no%20relayout%2C%20no%0A%20%20%20%20nodes%20jumping%20about.%20Only%20structural%20changes%20re-run%20Graphviz.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20quantity%20%3D%20mo.ui.slider(1%2C%2025%2C%20value%3D12%2C%20step%3D1%2C%20label%3D%22Quantity%22)%0A%20%20%20%20shock%20%3D%20mo.ui.slider(-0.30%2C%200.0%2C%20value%3D-0.08%2C%20step%3D0.01%2C%20label%3D%22Shock%22)%0A%20%20%20%20auto_compute%20%3D%20mo.ui.checkbox(value%3DTrue%2C%20label%3D%22Compute%20after%20each%20change%22)%0A%20%20%20%20return%20auto_compute%2C%20quantity%2C%20shock%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(auto_compute%2C%20mo%2C%20quantity%2C%20shock)%3A%0A%20%20%20%20mo.hstack(%5Bquantity%2C%20shock%2C%20auto_compute%5D%2C%20justify%3D%22start%22%2C%20gap%3D2)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(auto_compute%2C%20book%2C%20quantity%2C%20shock%2C%20widget)%3A%0A%20%20%20%20%23%20Referencing%20%60%60widget%60%60%20makes%20the%20subscription%20an%20explicit%20dependency%20of%0A%20%20%20%20%23%20this%20cell.%20The%20widget%20object%20is%20not%20recreated%20when%20a%20slider%20moves.%0A%20%20%20%20_widget_revision_before_update%20%3D%20widget.revision%0A%20%20%20%20book.insert_many(%5B(%22position%2Fquantity%22%2C%20int(quantity.value))%2C%20(%22risk%2Fshock%22%2C%20float(shock.value))%5D)%0A%20%20%20%20if%20auto_compute.value%3A%0A%20%20%20%20%20%20%20%20book.compute_all()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20The%20subscription%20API%0A%0A%20%20%20%20%60Computation.subscribe()%60%20is%20public%2C%20and%20useful%20without%20the%20widget.%20Each%0A%20%20%20%20outermost%20public%20mutation%20publishes%20one%20batched%20event%2C%20so%20%60insert_many()%60%0A%20%20%20%20and%20%60compute_all()%60%20produce%20a%20single%20row%20each%20rather%20than%20one%20per%20node.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(book%2C%20mo)%3A%0A%20%20%20%20get_event_log%2C%20set_event_log%20%3D%20mo.state(%5B%5D)%0A%0A%20%20%20%20def%20capture_event(event)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Record%20one%20batched%20computation%20event.%22%22%22%0A%20%20%20%20%20%20%20%20set_event_log(%0A%20%20%20%20%20%20%20%20%20%20%20%20lambda%20events%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20*events%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22revision%22%3A%20event.revision%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22changed%22%3A%20len(event.changed_nodes)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22graph%20changed%22%3A%20event.graph_changed%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22nodes%22%3A%20%22%2C%20%22.join(sorted(str(node)%20for%20node%20in%20event.changed_nodes))%5B%3A80%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20book.subscribe(capture_event)%0A%20%20%20%20return%20(get_event_log%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(get_event_log%2C%20mo)%3A%0A%20%20%20%20mo.ui.table(list(reversed(get_event_log()%5B-8%3A%5D))%2C%20selection%3DNone%2C%20pagination%3DFalse)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20It%20looks%20like%20part%20of%20the%20app%0A%0A%20%20%20%20The%20widget%20takes%20its%20colours%20from%20the%20page%20it%20is%20embedded%20in%20rather%20than%0A%20%20%20%20bringing%20its%20own.%20On%20load%20it%20reads%20the%20host's%20background%3A%20the%20brightness%20of%0A%20%20%20%20that%20decides%20light%20or%20dark%2C%20which%20is%20more%20reliable%20than%0A%20%20%20%20%60prefers-color-scheme%60%20because%20marimo's%20theme%20toggle%20never%20touches%20the%0A%20%20%20%20operating%20system%20setting.%0A%0A%20%20%20%20Marimo%20also%20publishes%20a%20full%20palette%20as%20CSS%20custom%20properties%2C%20and%20those%0A%20%20%20%20inherit%20through%20the%20widget's%20shadow%20boundary%2C%20so%20the%20widget%20wears%20marimo's%0A%20%20%20%20own%20%60--background%60%2C%20%60--card%60%2C%20%60--foreground%60%2C%20%60--border%60%20and%20%60--primary%60%0A%20%20%20%20instead%20of%20approximating%20them.%20Switch%20the%20notebook%20between%20light%20and%20dark%0A%20%20%20%20and%20the%20widget%20follows.%0A%0A%20%20%20%20The%20names%20are%20generic%20enough%20that%20another%20design%20system%20could%20mean%20something%0A%20%20%20%20else%20by%20them%2C%20so%20they%20are%20adopted%20only%20once%20the%20host's%20declared%0A%20%20%20%20%60--background%60%20matches%20the%20backdrop%20it%20actually%20paints.%20Anywhere%20else%2C%20the%0A%20%20%20%20widget%20falls%20back%20to%20its%20own%20palette.%0A%0A%20%20%20%20The%20graph%20keeps%20a%20light%20sheet%20in%20both%20themes%20%E2%80%94%20Graphviz%20paints%20black%20labels%0A%20%20%20%20onto%20it%20%E2%80%94%20but%20the%20pane%20behind%20that%20sheet%20takes%20the%20host%20colour%2C%20so%20the%20graph%0A%20%20%20%20reads%20as%20paper%20on%20the%20page%20rather%20than%20a%20white%20hole%20in%20a%20dark%20app.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Read-only%20mode%0A%0A%20%20%20%20%60editable%3DFalse%60%20removes%20the%20edit%20and%20compute%20controls.%20Navigation%20stays%2C%0A%20%20%20%20because%20opening%20a%20block%20inspects%20the%20graph%20rather%20than%20mutating%20it.%20The%0A%20%20%20%20guard%20is%20enforced%20in%20Python%2C%20not%20just%20hidden%20in%20the%20browser.%0A%0A%20%20%20%20This%20one%20also%20passes%20%60fit_on_render%3DTrue%60%2C%20so%20it%20scales%20to%20show%20the%20whole%0A%20%20%20%20graph%20whenever%20it%20re-renders%20instead%20of%20opening%20at%20natural%20size.%20It%20only%0A%20%20%20%20ever%20shrinks%20%E2%80%94%20blowing%20a%20small%20graph%20up%20to%20fill%20the%20pane%20is%20not%20what%20fit%0A%20%20%20%20means%20%E2%80%94%20so%20it%20is%20worth%20turning%20on%20when%20the%20shape%20matters%20more%20than%20the%0A%20%20%20%20labels.%0A%0A%20%20%20%20This%20is%20a%20second%20widget%20on%20the%20same%20computation%3A%20both%20follow%20it%2C%20and%20both%0A%20%20%20%20stay%20in%20step.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(book%2C%20mo)%3A%0A%20%20%20%20read_only%20%3D%20book.widget(editable%3DFalse%2C%20fit_on_render%3DTrue)%0A%20%20%20%20read_only_ui%20%3D%20mo.ui.anywidget(read_only)%0A%20%20%20%20return%20(read_only_ui%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20read_only_ui)%3A%0A%20%20%20%20_static_export%20%3D%20%22data%3Atext%2Fjavascript%22%20in%20read_only_ui.text%0A%20%20%20%20mo.Html(read_only_ui.widget.graph_svg)%20if%20_static_export%20else%20read_only_ui%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Direction%20and%20focus%0A%0A%20%20%20%20Two%20controls%20make%20a%20deep%20graph%20navigable.%0A%0A%20%20%20%20**Direction.**%20The%20graph%20is%20laid%20out%20left%20to%20right%20by%20default%2C%20the%20way%20a%0A%20%20%20%20computation%20reads%3A%20inputs%20on%20the%20left%2C%20results%20on%20the%20right.%20The%20**LR**%0A%20%20%20%20button%20in%20the%20toolbar%20flips%20it%20to%20**TB**%20and%20back.%20Pass%0A%20%20%20%20%60comp.widget(rankdir%3D%22TB%22)%60%20to%20start%20the%20other%20way.%0A%0A%20%20%20%20**Opening.**%20Clicking%20a%20block%20opens%20it%20where%20it%20stands%2C%20so%20its%20insides%0A%20%20%20%20appear%20beside%20its%20neighbours%20with%20the%20edges%20between%20them%20drawn.%20That%20is%0A%20%20%20%20usually%20the%20reason%20to%20look%20inside%20a%20block%20at%20all.%20Click%20its%20title%20to%20close%0A%20%20%20%20it%20again.%0A%0A%20%20%20%20**Isolating.**%20Blocks%20can%20nest%20%E2%80%94%20here%20a%20book%20holds%20desks%2C%20and%20each%20desk%0A%20%20%20%20holds%20instruments.%20Opening%20every%20level%20at%20once%20is%20a%20wall%20of%20nodes%2C%20so%0A%20%20%20%20**alt-click**%20a%20block%20instead%3A%20it%20becomes%20the%20root%20and%20the%20graph%20shows%20only%0A%20%20%20%20its%20top%20layer%2C%20exactly%20as%20clicking%20a%20folder%20shows%20that%20folder.%20What%20is%0A%20%20%20%20inside%20is%20blocks%20again%2C%20so%20you%20keep%20alt-clicking%20down.%0A%0A%20%20%20%20A%20breadcrumb%20appears%20under%20the%20toolbar.%20Click%20any%20step%20to%20climb%20back%20out%2C%0A%20%20%20%20**Reset**%20to%20return%20to%20the%20top%2C%20or%20**Compute**%20to%20compute%20every%20node%20in%20the%0A%20%20%20%20block%20you%20are%20standing%20in.%0A%0A%20%20%20%20Try%20it%3A%20alt-click%20%60emea%60%2C%20then%20%60rates%60%20inside%20it.%20The%20breadcrumb%20reads%0A%20%20%20%20**Reset%20%E2%80%BA%20emea%20%E2%80%BA%20rates**.%20Going%20back%20out%20is%20instant%20%E2%80%94%20the%20widget%20keeps%20the%0A%20%20%20%20layouts%20it%20has%20already%20drawn.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(loman)%3A%0A%20%20%20%20def%20build_desk()%3A%0A%20%20%20%20%20%20%20%20%22%22%22A%20book%20of%20desks%2C%20each%20holding%20instruments%20%E2%80%94%20three%20levels%20of%20blocks.%22%22%22%0A%20%20%20%20%20%20%20%20comp%20%3D%20loman.Computation()%0A%20%20%20%20%20%20%20%20legs%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22emea%2Frates%2Fswap%22%3A%200.031%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22emea%2Frates%2Fbond%22%3A%200.028%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22emea%2Fcredit%2Fcds%22%3A%200.045%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22apac%2Frates%2Fswap%22%3A%200.026%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22apac%2Fequity%2Ffuture%22%3A%200.052%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20for%20path%2C%20rate%20in%20legs.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20comp.add_node(f%22%7Bpath%7D%2Fnotional%22%2C%20value%3D1_000_000.0)%0A%20%20%20%20%20%20%20%20%20%20%20%20comp.add_node(f%22%7Bpath%7D%2Frate%22%2C%20value%3Drate)%0A%20%20%20%20%20%20%20%20%20%20%20%20comp.add_node(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Bpath%7D%2Fpv%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lambda%20notional%2C%20rate%3A%20notional%20*%20rate%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20kwds%3D%7B%22notional%22%3A%20f%22%7Bpath%7D%2Fnotional%22%2C%20%22rate%22%3A%20f%22%7Bpath%7D%2Frate%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20comp.add_node(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22book_pv%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20lambda%20**pvs%3A%20sum(pvs.values())%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20kwds%3D%7Bpath.replace(%22%2F%22%2C%20%22_%22)%3A%20f%22%7Bpath%7D%2Fpv%22%20for%20path%20in%20legs%7D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20comp.compute_all()%0A%20%20%20%20%20%20%20%20return%20comp%0A%0A%20%20%20%20desk%20%3D%20build_desk()%0A%20%20%20%20return%20(desk%2C)%0A%0A%0A%40app.cell%0Adef%20_(desk%2C%20mo)%3A%0A%20%20%20%20desk_widget%20%3D%20desk.widget()%0A%20%20%20%20desk_ui%20%3D%20mo.ui.anywidget(desk_widget)%0A%20%20%20%20return%20(desk_ui%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(desk_ui%2C%20mo)%3A%0A%20%20%20%20_static_export%20%3D%20%22data%3Atext%2Fjavascript%22%20in%20desk_ui.text%0A%20%20%20%20mo.Html(desk_ui.widget.graph_svg)%20if%20_static_export%20else%20desk_ui%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20A%20widget%20on%20a%20section%20of%20the%20graph%0A%0A%20%20%20%20You%20do%20not%20have%20to%20render%20the%20whole%20computation.%20Pass%20%60root%3D%60%20to%20build%20a%0A%20%20%20%20widget%20scoped%20to%20one%20block%2C%20and%20only%20that%20section%20is%20drawn%20%E2%80%94%20its%20inputs%20from%0A%20%20%20%20the%20rest%20of%20the%20graph%20still%20resolve%2C%20they%20are%20simply%20out%20of%20view.%0A%0A%20%20%20%20This%20is%20the%20static%20counterpart%20to%20isolating%20a%20block%3A%20%60root%3D%60%20fixes%20the%0A%20%20%20%20section%20up%20front%2C%20while%20alt-clicking%20re-roots%20a%20full-graph%20widget%20as%20you%0A%20%20%20%20explore.%20Both%20use%20the%20same%20paths%2C%20so%20%60desk.widget(root%3D%22emea%22)%60%20opens%0A%20%20%20%20exactly%20where%20alt-clicking%20%60emea%60%20lands.%0A%0A%20%20%20%20Scoping%20keeps%20a%20dashboard%20tight%3A%20put%20the%20desk%20you%20own%20in%20one%20widget%20and%20a%0A%20%20%20%20neighbouring%20desk%20in%20another%2C%20each%20a%20live%20view%20of%20the%20same%20%60Computation%60.%20The%0A%20%20%20%20selection%20is%20still%20the%20real%20thing%20%E2%80%94%20%60section.selected_name%60%20reports%20the%20full%0A%20%20%20%20path%2C%20%60emea%2Frates%60%2C%20not%20a%20name%20local%20to%20the%20section.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(desk%2C%20mo)%3A%0A%20%20%20%20section%20%3D%20desk.widget(root%3D%22emea%22)%0A%20%20%20%20section_ui%20%3D%20mo.ui.anywidget(section)%0A%20%20%20%20return%20(section_ui%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20section_ui)%3A%0A%20%20%20%20_static_export%20%3D%20%22data%3Atext%2Fjavascript%22%20in%20section_ui.text%0A%20%20%20%20mo.Html(section_ui.widget.graph_svg)%20if%20_static_export%20else%20section_ui%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Building%20the%20graph%2C%20not%20only%20watching%20it%0A%0A%20%20%20%20Everything%20above%20changes%20the%20values%20in%20a%20computation.%20%60buildable%3DTrue%60%0A%20%20%20%20lets%20the%20widget%20build%20the%20computation%20itself%3A%20**%2B%20Node**%20in%20the%20toolbar%20opens%0A%20%20%20%20a%20form%2C%20and%20a%20selected%20node%20grows%20**Edit**%2C%20**Rename**%20and%20**Delete**.%0A%0A%20%20%20%20A%20node%20is%20one%20of%20two%20things.%20An%20**input**%20is%20a%20name%20and%20optionally%20a%20scalar%20%E2%80%94%0A%20%20%20%20with%20no%20value%20it%20starts%20%60UNINITIALIZED%60%2C%20which%20is%20how%20you%20declare%20an%20input%20to%0A%20%20%20%20supply%20later.%20A%20**calculation**%20is%20a%20name%2C%20a%20list%20of%20inputs%20and%20a%20Python%0A%20%20%20%20expression%3A%20each%20input%20becomes%20a%20parameter%20of%20the%20compiled%20function%20*and*%20an%0A%20%20%20%20edge%20in%20the%20graph%2C%20so%20%60price%60%20and%20%60quantity%60%20with%20%60price%20*%20quantity%60%20is%20the%0A%20%20%20%20same%20node%20%60add_node%60%20would%20have%20built.%0A%0A%20%20%20%20Try%20it%20on%20the%20two%20inputs%20below.%20Add%20a%20calculation%20called%20%60interest%60%2C%20with%0A%20%20%20%20%60notional%60%20and%20%60rate%60%20as%20its%20inputs%20and%20%60notional%20*%20rate%60%20as%20its%20expression%3B%0A%20%20%20%20then%20press%20**Compute%20all**.%0A%0A%20%20%20%20Names%20are%20read%20against%20the%20block%20in%20focus%2C%20so%20a%20name%20typed%20while%20inside%20a%0A%20%20%20%20block%20lands%20in%20it%2C%20and%20a%20leading%20%60%2F%60%20reaches%20back%20out.%20That%20is%20also%20how%20a%0A%20%20%20%20block%20gets%20built%20%E2%80%94%20there%20is%20no%20%22new%20block%22%20button%2C%20because%20a%20block%20is%20a%0A%20%20%20%20naming%20convention%3A%20add%20%60market%2Fspot%60%20and%20%60market%2Fvol%60%20and%20the%20block%20appears.%0A%0A%20%20%20%20%60namespace%3D%60%20is%20what%20puts%20your%20own%20imports%20in%20scope%20for%20an%20expression%2C%20so%0A%20%20%20%20%60np.sqrt(notional)%60%20works%20below.%20Two%20things%20worth%20knowing%3A%20this%20compiles%20and%0A%20%20%20%20runs%20an%20expression%20written%20in%20the%20browser%20inside%20the%20kernel%2C%20which%20is%20why%20it%0A%20%20%20%20is%20opt-in%20rather%20than%20part%20of%20%60editable%60%3B%20and%20a%20function%20built%20this%20way%20has%0A%20%20%20%20no%20importable%20path%2C%20so%20%60comp.save()%60%20keeps%20its%20value%20but%20not%20its%20function.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(loman%2C%20mo%2C%20np)%3A%0A%20%20%20%20sketch%20%3D%20loman.Computation()%0A%20%20%20%20sketch.add_node(%22notional%22%2C%20value%3D1_000_000.0)%0A%20%20%20%20sketch.add_node(%22rate%22%2C%20value%3D0.045)%0A%20%20%20%20sketch_ui%20%3D%20mo.ui.anywidget(sketch.widget(collapse_all%3DFalse%2C%20buildable%3DTrue%2C%20namespace%3D%7B%22np%22%3A%20np%7D))%0A%20%20%20%20return%20(sketch_ui%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20sketch_ui)%3A%0A%20%20%20%20_static_export%20%3D%20%22data%3Atext%2Fjavascript%22%20in%20sketch_ui.text%0A%20%20%20%20mo.Html(sketch_ui.widget.graph_svg)%20if%20_static_export%20else%20sketch_ui%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Worth%20knowing%0A%0A%20%20%20%20**A%20bare%20%60comp%60%20stays%20a%20static%20picture.**%20Evaluating%20%60comp%60%20on%20its%20own%0A%20%20%20%20renders%20an%20SVG%20through%20%60_repr_svg_%60%2C%20and%20always%20will.%20Interactivity%20is%20opt-in%0A%20%20%20%20through%20%60comp.widget()%60.%0A%0A%20%20%20%20**Computation%20is%20synchronous.**%20Pressing%20a%20compute%20button%20runs%20in%20the%20kernel%2C%0A%20%20%20%20on%20the%20thread%20handling%20the%20request%2C%20so%20a%20slow%20graph%20freezes%20the%20widget%20while%0A%20%20%20%20it%20works.%20%60Computation%60%20is%20not%20thread-safe%2C%20so%20the%20widget%20deliberately%20does%0A%20%20%20%20not%20compute%20on%20a%20background%20thread.%20For%20long%20computations%2C%20drive%20them%20from%20an%0A%20%20%20%20ordinary%20cell%20and%20let%20the%20widget%20observe%20the%20result.%0A%0A%20%20%20%20**Large%20graphs.**%20Graphviz%20costs%20roughly%200.6%20KiB%20and%200.6%20ms%20per%20rendered%0A%20%20%20%20node%2C%20both%20linear.%20State%20changes%20repaint%20in%20place%20and%20re-send%20only%20a%20small%0A%20%20%20%20state%20map%2C%20about%2032%C3%97%20smaller%20than%20the%20SVG.%20To%20stop%20one%20click%20hanging%20the%0A%20%20%20%20kernel%2C%20the%20widget%20refuses%20to%20open%20a%20block%20that%20would%20put%20more%20than%20500%20nodes%0A%20%20%20%20on%20screen%3B%20raise%20it%20with%20%60comp.widget(max_rendered_nodes%3D2000)%60.%0A%0A%20%20%20%20**State%20colours.**%20Loman's%20default%20state%20colours%20are%20shared%20with%0A%20%20%20%20%60comp.draw()%60%20and%20are%20not%20colourblind-safe%20%E2%80%94%20%60STALE%60%20and%20%60COMPUTABLE%60%20are%0A%20%20%20%20close.%20The%20legend%20in%20the%20footer%20names%20every%20state%20on%20screen%20so%20colour%20never%0A%20%20%20%20carries%20the%20meaning%20alone.%20Pass%20%60cmap%3D%60%20for%20your%20own%20palette.%0A%0A%20%20%20%20**Closing.**%20ipywidgets%20keeps%20every%20open%20widget%20in%20a%20process-wide%20table%2C%20so%0A%20%20%20%20call%20%60widget.close()%60%20when%20finished%3B%20that%20unsubscribes%20it%20and%20releases%20it.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20The%20API%20in%20one%20place%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20widget%20%3D%20comp.widget()%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20mirrors%20comp.draw()%2C%20plus%20the%20options%20below%0A%20%20%20%20widget%20%3D%20comp.widget(root%3D%22emea%22)%20%20%20%20%23%20scope%20the%20widget%20to%20one%20block%0A%20%20%20%20comp.widget(rankdir%3D%22TB%22)%20%20%20%20%20%20%20%20%20%20%20%20%23%20start%20top-to-bottom%3B%20LR%20is%20the%20default%0A%20%20%20%20comp.widget(editable%3DFalse)%20%20%20%20%20%20%20%20%20%20%23%20inspect%20only%3B%20navigation%20still%20works%0A%20%20%20%20comp.widget(buildable%3DTrue)%20%20%20%20%20%23%20add%2C%20redefine%2C%20rename%20and%20delete%20nodes%0A%20%20%20%20comp.widget(namespace%3Dglobals())%20%20%20%20%20%23%20imports%20a%20built%20node's%20expression%20can%20use%0A%20%20%20%20comp.widget(fit_on_render%3DTrue)%20%20%20%20%20%20%23%20scale%20to%20the%20pane%20on%20every%20render%0A%20%20%20%20comp.widget(max_rendered_nodes%3D2000)%20%23%20raise%20the%20ceiling%20on%20opening%20a%20block%0A%0A%20%20%20%20widget.selected_name%20%20%20%20%20%20%20%20%20%20%20%20%23%20the%20real%20Loman%20name%2C%20not%20a%20browser%20ID%0A%20%20%20%20widget.selected_names%20%20%20%20%20%20%20%20%20%20%20%23%20every%20member%2C%20for%20a%20collapsed%20block%0A%20%20%20%20widget.full_view%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20node%20the%20Show%20full%20button%20published%2C%20or%20%22%5C%22%0A%20%20%20%20widget.full_view_value%20%20%20%20%20%20%20%20%20%20%23%20that%20node's%20value%2C%20or%20None%0A%20%20%20%20widget.refresh()%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20escape%20hatch%20after%20mutating%20comp.dag%20directly%0A%20%20%20%20widget.close()%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20unsubscribe%20and%20release%0A%0A%20%20%20%20unsubscribe%20%3D%20comp.subscribe(on_change)%20%20%20%23%20one%20batched%20event%20per%20mutation%0A%20%20%20%20comp.revision%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20monotonic%20change%20counter%0A%20%20%20%20%60%60%60%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
c6a01a8a54413d73b1045327c99058cb