Open vnijs opened 1 year ago
Unless I'm reading this wrong, I don't think you can currently set the width/height of the plot from the server side at all--it always looks at the width/height of the DOM element. Any chance you're up for taking a stab at a PR?
I see. Is it not possible to translate what you already have working for R-shiny?
It totally is possible, it’s not a big change, we just haven’t done it yet. I just thought you might not want to wait, in which case you could do it yourself. If you don’t feel up to it, that’s fine, we’ll try to get to it soon!
@jcheng5 Building apps using the great tools the shiny team creates is a sufficient challenge for me for now :) Still early days, but if you think an app would be of interest to add to an example gallery, let me know. First one is linked below.
https://github.com/vnijs/pyrsm/blob/main/pyrsm/radiant/regress.py
@vnijs
Does dynamic UI work for your use case? When necessary, the UI output could be re-rendered and can contain new height/width values. Then, a new plot will automatically be rendered when the UI output component is visible.
If parameter is adopted, I take it the height
function would also need to be reactive?
Would having the plot fill the available vertical space work? We have layout capabilities similar to https://rstudio.github.io/bslib/articles/filling.html#filling-the-window coming to py-shiny.
@schloerke I hadn't thought about dynamic ui. Is there any "cost" to re-rendering the ui elements instead of only changing the height setting? In my apps there could be 5-10 different types of plots the user would select from a drop down. See screenshots below. The first plot is fine and a fixed format but the second could have 20-30 different subplots depending on what the user selects.
Automatic height detection could be really interesting. Not sure if you mean the settings adapt to the size of the plot or if the size of the plot is adapted to the available space. I'd be interested in the former. I played around with streamlet a bit recently (super simple to use btw) and it seems to have that functionality built in by default (see example file linked below). This saves the app developer from having to manage plot dimensions and I liked that feature a lot. Would this work well together with sidebar and navbar layouts?
https://github.com/vnijs/pyrsm/blob/main/pyrsm/streamlet/streamlet-logistic-app.py
@vnijs I misunderstood the original request. Agreed, the height
variable (and others) should be able to be reactive.
I took the morning and rewrote your pyrsm streamlet-logistic-app.py
into a Shiny application.
app.py
Have the Plot tab (Tab 3) contain a dynamic UI that resolves to a plot output with statically defined height. When ever we want to recalculate the height, we recalculate a new container that has a new plot output component with altered specs. This approach isn't noticeably slower as the plot needs to be replotted with a the new height value.
Notes:
@vnijs
I hadn't thought about dynamic ui. Is there any "cost" to re-rendering the ui elements instead of only changing the height setting?
Until a proper fix is made, it will add a extra round trip on the WebSocket: to send the updated container contents and respond that a plot is visible and ready to be calculated. Then the standard plotting update will occur.
Automatic height detection could be really interesting. Not sure if you mean the settings adapt to the size of the plot or if the size of the plot is adapted to the available space. I'd be interested in the former.
It is the latter option: The size of the plot is adapted to the available space. Shiny does not inspect your plots and make judgement calls on how they should be displayed.
I played around with streamlet a bit recently (super simple to use btw) and it seems to have that functionality built in by default (see example file linked below). This saves the app developer from having to manage plot dimensions and I liked that feature a lot.
I did not see how your height was being dynamically set by streamlit. Can you point me to the lines of code? Thank you!
Would this work well together with sidebar and navbar layouts?
Assuming we're talking about Filling Layouts working with sidebars and different layouts... Yes! I have really enjoyed using them during development. Hoping to get the into py-shiny
's ui
within the month.
@schloerke Thanks a lot for the responses and the shiny version of the code! If you are able to run the streamlet app you should see that it sets the height automatically. See also the video linked below. The formatting/sizing of the plots is exactly how I'd want them and streamlet seems to set this automatically without the need for the user/app developer to set parameters.
BTW I really liked the "code" output object that streamlet has. The syntax highlighting isn't that great but still better than plain black text and it also has an easy to use "copy-to-clipboard" option which is nice. See screenshot below.
Thank you for the video!
It is interesting to see how (at render time) streamlit captures the current width and allows the height to grow naturally up to a max height.
Shiny (typically) fixes the plot container height and allows the plot container to expand its width. Given the final container size, the plot is rendered.
Yup... still curious how the plot height is determined within streamlit.
In matplotlib it looks like the figure itself can have opinions about its width/height (fig.get_figwidth
/fig.get_figheight
), that is in inches but there's also fig.get_dpi
. That is certainly useful information to Shiny, for aspect ratio purposes if nothing else!
That works really well @jcheng5! Combining your suggestion with @schloerke dynamic ui example above I came to the following, where make_plot
contains the plotting code you want to run. The only thing I would still like to fix is that when the browser window is narrow, the plot "breaks" out of the ui.column
frame I'm using.
@reactive.Calc
def gen_plot():
make_plot(...)
fig = plt.gcf() # get current plot
width, height = fig.get_size_inches() # Get the size in inches
return fig, width * 96, height * 96
@output(id="plot")
@render.plot
def plot():
plots = input.plots()
if plots != "None":
return gen_plot()[0]
@output(id="plot_container")
@render.ui
def plot_container():
req(...)
width, height = gen_plot()[1:]
return ui.output_plot("plot", height=f"{height}px", width=f"{width}px")
Hello, I appreciate great discussions above regarding this issue. Has there been any update to dynamically set width and/or height parameter values to the render.plot()
function, e.g., @render.plot(width=input.width(), height=input.height())
?
I am trying to reproduce R example for Dynamic Height and Width in Hadley Wickham's renderPlot()
's expr
argument.
I created the following app per @vnijs 's example above, but it still behave differently from original R example, because with the dynamic UI, random number generation inside the render function runs everytime when the width or height is changed.
from shiny import App, ui, render, reactive
import matplotlib.pyplot as plt
from numpy.random import normal
app_ui = ui.page_fluid(
ui.input_slider("height", "height", min=100, max=500, value=250),
ui.input_slider("width", "width", min=100, max=500, value=250),
ui.output_ui("plot_container"),
)
def server(input, output, session):
@render.plot
def plot():
return plt.scatter(normal(size=20), normal(size=20))
@render.ui
def plot_container():
return ui.output_plot("plot",
height=f"{input.height()}px",
width=f"{input.width()}px")
app = App(app_ui, server)
R-shiny has an option to specify a function that can be used to set the height of the plot window. I expected something similar to work for py-shiny with
@render.plot
as below. However, that returns an error (try_render_matplotlib() got multiple values for argument 'height'
)Questions: Is there a way to set plot window height and width dynamically in py-shiny?
If this is a question better posted to posit community, please let me know.