Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(maidr.show): support py-shiny renderer #67

Merged
merged 7 commits into from
Aug 24, 2024

Conversation

krishnanand5
Copy link
Collaborator

This PR adds support for maidr on py-shiny through a custom renderer called render_maidr. This decorator internally calls the newly created maidr.render function to transform a plot of the matplotlib type to a maidr object and envelops it as an iframe srcdoc for display on a py-shiny web dashboard.

Closes [#65]

@jooyoungseo
Copy link
Member

@krishnanand5 I think you have accidentally closed this PR. Please reopen.

@krishnanand5
Copy link
Collaborator Author

Greetings Professor,

In an attempt to resolve a merge conflict with poetry.lock, I synced my fork and it caused this unforeseen change. I am rectifying my local repo and will be making a commit shortly with the updated scripts.

Regards,
Krishna Anandan Ganesan.

@jooyoungseo
Copy link
Member

jooyoungseo commented Aug 14, 2024 via email

@krishnanand5 krishnanand5 reopened this Aug 14, 2024
@krishnanand5
Copy link
Collaborator Author

Greetings Professor,

I have addressed all the initial concerns in the respective sections and would like to discuss one change that I have implemented in the shiny_renderer.

As shiny provides complete freedom to the user to define the cosmetics of a component that they are creating, the same applies to plot objects as well. In order to view a maidr plot, we require the component area to be slightly bigger so that the user can experience the interactiveness without having to use scrollbars - in other words, overflow of the web component is a factor to consider/document.

For now, I have modified the render to expect a width and height parameter during function invocation. If you feel we should handle this in a better way, please do suggest any measures and I will begin working on them. I am investigating htmltools to identify any support that could help us achieve this.

Regards,
Krishna Anandan Ganesan.

@jooyoungseo
Copy link
Member

@SaaiVenkat and @dakshpokar -- Please review this PR as well.

@jooyoungseo jooyoungseo reopened this Aug 16, 2024
Copy link
Member

@jooyoungseo jooyoungseo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@jooyoungseo
Copy link
Member

I will merge this PR after #64 is fully addressed.


def wrapper(
*args: Any,
width: Optional[str] = None,
Copy link
Collaborator

@dakshpokar dakshpokar Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @krishnanand5,
Can you automatically resize the iframe if height and width are not passed as arguments? Please feel free to refer to #64 for guidance. Considering this library's target audience, we should try to ensure the interface is as user-friendly and accessible as possible.

Apart from that the PR looks clean, good work!

As for the comment that you left on #64 - My observation is that Python notebooks typically have a considerable level of flexibility when it comes to handling cell rendering, which makes suitable style changes possible.

To handle this, you may want to try utilizing Inline-JS/CSS to more accurately regulate the overflow behavior.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Since iframe seems to addressed here as well, I would suggest to merge this PR after feat(maidr.show): support interactive maidr plots within Jupyter Notebooks, VS Code, Google Colab, and Quarto #64 .
  • Also, please create a package called 'widget' under 'maidr' package (maidr/widget/shiny.py), and move the contents there. We'll be extending this further to support GUI widgets in the future.
  • We are using the htmltools ui underneath, and shiny uses the same. This annotation duplicates many of the functionalities from shiny as well as from htmltools. This could be simplified by the following
from __future__ import annotations

from shiny.render import ui
from shiny.types import Jsonifiable

import maidr


class render_maidr(ui):
    async def render(self) -> Jsonifiable:
        value = await self.fn()
        if value is None:
            return None

        value = maidr.render(value)
        rendered = await self.transform(value)
        return rendered

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requested changes have been addressed in in this commit.


def wrapper(
*args: Any,
width: Optional[str] = None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Since iframe seems to addressed here as well, I would suggest to merge this PR after feat(maidr.show): support interactive maidr plots within Jupyter Notebooks, VS Code, Google Colab, and Quarto #64 .
  • Also, please create a package called 'widget' under 'maidr' package (maidr/widget/shiny.py), and move the contents there. We'll be extending this further to support GUI widgets in the future.
  • We are using the htmltools ui underneath, and shiny uses the same. This annotation duplicates many of the functionalities from shiny as well as from htmltools. This could be simplified by the following
from __future__ import annotations

from shiny.render import ui
from shiny.types import Jsonifiable

import maidr


class render_maidr(ui):
    async def render(self) -> Jsonifiable:
        value = await self.fn()
        if value is None:
            return None

        value = maidr.render(value)
        rendered = await self.transform(value)
        return rendered

maidr/maidr.py Outdated
@@ -10,6 +11,14 @@
from maidr.core.figure_manager import FigureManager


def render(
plot: Any, *, lib_prefix: str | None = "lib", include_version: bool = True
) -> RenderedHTML:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to incorporate the simplified rendering we can return the Tag object instead of the RenderedHTML.

def render(plot: Any) -> Tag:
    ax = FigureManager.get_axes(plot)
    maidr = FigureManager.get_maidr(ax.get_figure())
    return maidr.render()

And update core/maidr.py to render the Tag

    def render(self) -> Tag:
        return self._create_html_tag()

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @SaaiVenkat, for these suggestions. These changes have been incorporated and have made the module more compact


# Define the server
def server(input, output, session):
@reactive.Calc
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reactive.Calc is mainly used to perform mathematical intensive calculations. May I know why this has been used here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, reactive.calc is residual from development. Will remove this from the example

return s_plot

@output
@render.ui
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This plot has been injected into the shiny through ui.output_ui. Why @output is required?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@output is for rendering reactive output on the UI but since @render.ui is sufficient in our case to render the generated HTML on the shiny app, @output is redundant. Will have this removed.

@output
@render.ui
def reactivebarplot():
return render_maidr(create_reactivebarplot)(width="100%", height="700px")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could @render_maidr be used to render the example? If so, please update this to reflect the same.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @render_maidr is a decorator, we can invoke this directly.

@krishnanand5
Copy link
Collaborator Author

Greetings @SaaiVenkat,

Following are the list of changes I have done in the recent commits:

  • Moved shiny_renderer.py to maidr/widget/shiny.py.
  • Updated the example plot to use only render_maidr and not use default shiny renderers like @output and @render.ui.
  • @reactive.calc served no purpose for the provided example script and has been removed.
  • shiny.py has been simplified to use the render of maidr.
  • As discussed, I have removed the conditional check in _inject_plot to allow shiny to utilize the same iframe generator as the one @dakshpokar developed for Interactive Python notebooks for Shiny as well.

I did check possible collateral effects of removing the conditional check but turns out only the interactive python module and shiny module are currently leveraging it for iframes. Please let me know if the changes made are as per expectations and if there are any further changes you would like on this module.

Regards,
Krishna Anandan Ganesan.

import maidr


class render_maidr(ui):
Copy link
Member

@jooyoungseo jooyoungseo Aug 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use PascalCase for class name in our package. Why do you use snake_case here? Is this the Shiny convention?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked Shiny and there are no such conventions. I have renamed the class name to adhere to PascalCase.

import maidr


class RenderMaidr(ui):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krishnanand5 shiny breaks the conventional Pascal case for classes, since this will be used as a decorator, like @render_maidr. There have followe this convention for all their renderers. So, we should also follow their convention.

cc: @jooyoungseo

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the confirmation @SaaiVenkat. I thought our repository's naming convention superseded supporting libraries but you are right. Better to follow the naming convention of the supporting libraries. I have reverted the class name to snake_case

@SaaiVenkat SaaiVenkat merged commit a944826 into xability:main Aug 24, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants