Beyond R Shiny: Shiny for Python's Clean Design for Dynamic Plot Management

Reading time:
time
min

Shiny for Python represents a significant advancement in the field of data dashboarding, setting new standards for design, maintainability, and scalability. This framework is not just an extension of R Shiny to Python but a refined approach that encapsulates the best practices learned from years of dashboard development:

  • Modular Design: Shiny for Python's architecture promotes clean, maintainable code through its emphasis on modularity, akin to principles seen in functional programming.
  • Scalability and Maintainability: Unlike traditional methods in R Shiny, Shiny for Python enforces scalable and maintainable solutions, making it easier to manage dynamic content like multiple plots.
  • Lessons from R Shiny: Building on R Shiny's legacy, Shiny for Python addresses past limitations by fostering best practices in software design from the outset.
  • Future-Proof Technology: Shiny for Python is not just a contemporary solution but a future-forward framework, designed to adapt to the evolving demands of data visualization.

This article encapsulates the essence of Shiny for Python as not just a tool, but a beacon for the future of data dashboarding, merging quality, innovation, and sustainability. For those looking to harness the power of this advanced framework, Appsilon stands ready to provide expert guidance and support.

Didn’t know Shiny is available for Python? Make sure to read our introduction article first.

A Brief Primer in Shiny for Python

In the evolving landscape of Python-based dashboarding tools, Shiny for Python emerges as a noteworthy contender, especially for those familiar with the R Shiny framework. Developed by the team at Posit (formerly RStudio), Shiny for Python aims to bring the interactive, web application framework capabilities of R Shiny to the Python ecosystem, catering to a broad spectrum of data science and analytics needs.

Orbit Simulation

Frameworks like Streamlit and Dash have carved their niches within the Python community, Shiny for Python distinguishes itself through its unique approach to application structure and reactivity.

Streamlit, known for its simplicity and ease of use, is ideal for quickly spinning up data applications. However, its model of re-running the entire script upon any input change poses scalability issues for more complex applications. This reactivity model, or rather the lack thereof, limits Streamlit's applicability for larger, more interactive applications where efficiency and control over execution flow are paramount.

Dash, on the other hand, offers extensive customization options and is a powerful tool for building sophisticated web applications. Despite its strengths, Dash's manual trigger system for updates can introduce additional complexity in managing the reactivity and interactivity of components, making the development process more cumbersome for applications requiring high levels of interactivity.

Shiny for Python aims to bridge these gaps by providing a framework that supports scalable, interactive web applications with a structured approach to reactivity and modularization. It encourages clean design and maintainable code, principles that are sometimes more challenging to adhere to in Streamlit and Dash.

For those familiar with R Shiny, transitioning to Shiny for Python can be intuitive, yet it's important to appreciate the nuanced differences and enhancements Shiny for Python offers. A practical tutorial by Winston Chang (one of the core developers behind Py and RShiny), which guides developers through translating R Shiny applications to Shiny for Python, serves as an excellent resource for those looking to dive deeper into Shiny for Python's capabilities. 

As we go further into Shiny for Python's approach to dynamic plot management, it's essential to keep in mind the broader context of Python dashboarding frameworks. Shiny for Python, with its emphasis on modularity and maintainability, offers an appealing path for developers seeking to build or transition to scalable, interactive web applications.

We recently released Tapyr, a framework for deployment ready Shiny for Python applications. Learn more about it in this blog post.

The Challenge: Managing Dynamic Content in Shiny for Python Dashboards

At Appsilon, we hold software craftsmanship in high regard, understanding deeply that the maintainability of code is not just a preference but a critical necessity in the long-term success of any project. A common hurdle we often face in dashboard development is the effective management of dynamic content, such as displaying a variable number of plots or images based on user input. 

This task tests not only the flexibility and scalability of a dashboarding framework but also its ability to uphold our standards for clean, manageable codebases. As user interactions within applications grow more complex, it becomes imperative to ensure that the application remains responsive and intuitive, without compromising on performance. 

This challenge is a testament to our commitment to finding and utilizing frameworks like Shiny for Python, which allow us to elegantly handle dynamic elements, seamlessly updating the user interface in real time to reflect changes, all while keeping the underlying code structured and in line with our principles of software excellence.

Interested in steps Appsilon takes to deliver impactful projects? Here's a blog post on what it's like working with us.

R Shiny's Approach: A Case Study

Let's examine a typical R Shiny example by Winston Chang first. The code snippet demonstrates a Shiny application featuring a sidebar with a slider input to control the number of plots displayed in the main panel.

max_plots <- 5

ui <- fluidPage(

  headerPanel("Dynamic number of plots"),

  sidebarPanel(
    sliderInput("n_plots", "Number of plots", value=1, min=1, max=max_plots)
  ),

  mainPanel(
    # This is the dynamic UI for the plots
    uiOutput("plots")
  )
)

server <- function(input, output) {

  # Insert the right number of plot output objects into the web page
  output$plots <- renderUI({
    plot_output_list <- lapply(1:input$n_plots, function(i) {
      plotname <- paste("plot", i, sep="")
      plotOutput(plotname)
    })

    # Convert the list to a tagList - this is necessary for the list of items
    # to display properly.
    do.call(tagList, plot_output_list)
  })

  # Call renderPlot for each one. Plots are only actually generated when they
  # are visible on the web page.
  for (i in 1:max_plots) {
    # Need local so that each item gets its own number. Without it, the value
    # of i in the renderPlot() will be the same across all instances, because
    # of when the expression is evaluated.
    local({
      my_i <- i
      plotname <- paste("plot", my_i, sep="")

      output[[plotname]] <- renderPlot({
        plot(1:my_i, 1:my_i,
             xlim = c(1, max_plots),
             ylim = c(1, max_plots),
             main = paste("1:", my_i, ".  n is ", input$n_plots, sep = "")
        )
      })
    })
  }
}

shinyApp(ui, server)

This approach hinges on dynamically modifying the output object within the server function to render the appropriate number of plot outputs. The uiOutput in the main panel doesn't refer to a single plot but to an entire section of the UI designated for these plots. As the user adjusts the slider, the server function responds by generating a list of plot output objects (plotOutput) with uniquely constructed names, which are then rendered and displayed.

While this method is straightforward and widely used, it has limitations in scalability and maintainability. Each plot is essentially added manually by crafting a unique string name and binding it to a renderPlot call within a loop. This manual handling of UI elements can become cumbersome and error-prone as the complexity of the application grows.

Moreover, although R Shiny offers modules as a means to encapsulate and reuse UI and server logic, their use isn't mandatory. Many developers opt for the direct manipulation of the output object, as seen in this example, due to its simplicity and immediate results. 

This short-term convenience usually leads to long-term scalability challenges, as the application's structure becomes more difficult to manage and extend. This case study underscores the importance of considering not just the immediate ease of implementation but also the future implications on the codebase's maintainability and adaptability.

Learn more about Shiny for Python - From an R Shiny Developer point of view.

Shiny for Python's Elegant Solution

In contrast to the R Shiny example, Shiny for Python offers a structured and modular approach to managing dynamic content, such as a variable number of plots. Shiny for Python utilizes the concept of modules extensively, which is a core part of its design philosophy, promoting reusability and maintainability.

import matplotlib.pyplot as plt
import numpy as np
from shiny import ui, render, req, App, module

MAX_PLOTS = 5


# UI module for a single plot
@module.ui
def plot_ui():
    # Returns a placeholder for a plot that will be rendered server-side
    return ui.output_plot("plot")


# Server module to generate and render each plot
@module.server
def plot_server(input, output, session, plot_id: int, max_plots: int = MAX_PLOTS):
    @render.plot
    def plot():
        # Generate data for the plot based on the plot_id
        x = np.r_[1 : (plot_id + 1)]
        y = np.r_[1 : (plot_id + 1)]

        fig, ax = plt.subplots()
        ax.scatter(x, y)
        ax.set_xlim(0, max_plots)
        ax.set_ylim(0, max_plots)
        ax.set_title(f"1: {plot_id}. n is {max_plots}")
        return fig


app_ui = ui.page_fluid(
    ui.panel_title("Dynamic number of plots"),
    ui.layout_sidebar(
        ui.sidebar(
            ui.input_slider(
                "n_plots", "Number of plots", value=1, min=1, max=MAX_PLOTS
            ),
        ),
        # Main panel to display all plots
        ui.output_ui("plots"),
    ),
)


def server(input, output, session):
    # Function to dynamically render UI components (plots)
    @render.ui
    def plots():
        # Ensure value is an integer and available
        n_plots = int(req(input.n_plots()))
        # Dynamically create server-side functions for each plot
        for i in range(1, n_plots + 1):
            plot_server(f"plot_{i}", plot_id=i, max_plots=MAX_PLOTS)
        # Dynamically create UI components for each plot
        # Note that the names must match the server-side functions
        # But they're close to each other in code
        return ([plot_ui(f"plot_{i}") for i in range(1, n_plots + 1)],)


app = App(app_ui, server)

The provided Python code showcases a Shiny for Python application that dynamically renders a specified number of plots based on user input, similar to the R Shiny case study. However, the implementation leverages Shiny for Python's modules to encapsulate both the UI and server logic for individual plots, demonstrating a cleaner and more scalable solution.

Each plot is represented by a UI module (plot_ui) that defines a placeholder for the plot, and a corresponding server module (plot_server) that contains the logic to generate and render the plot based on a unique plot_id. This separation of concerns allows for each plot component to be self-contained, enhancing code readability and making the application easier to extend and maintain.

The main application (app_ui and server) dynamically constructs the UI components and server logic for the number of plots specified by the user. This dynamic generation of content is handled elegantly within the Shiny for Python framework, without the need for manually manipulating output names or directly modifying the output object. Instead, the modular structure allows for a clear definition of how each plot should be rendered and where it should be displayed within the application.

This example highlights the framework's alignment with good software design practices. The modular approach inherently encourages developers to write cleaner, more organized code, which is crucial for building scalable and maintainable applications.

For a deeper dive into the workings of modules in Shiny for Python, the documentation on shiny modules provides comprehensive insights and examples, further demonstrating the framework's capabilities and advantages over traditional methods.

By adopting Shiny for Python's modular design, developers can achieve a high degree of interactivity and dynamism in their applications, all while maintaining a clean and manageable codebase. 

The Design Advantage: Architectural Benefits of Shiny for Python

Shiny for Python represents a significant stride towards maturity in the dashboarding domain, epitomizing the lessons learned from its R counterpart. It's not about reinventing the wheel but rather refining it to roll more smoothly. The framework is built on the foundation of promoting best practices and making it inherently challenging to deviate towards less maintainable code structures.

One of the core principles Shiny for Python advocates for is modularity. This approach is akin to the principles of functional programming, where the emphasis is on immutability and pure functions. Just as functional programming encourages developers to avoid mutating values to enhance predictability and reduce side effects, Shiny for Python's modular design discourages direct manipulation of global state and ad-hoc UI modifications. This constraint, far from being a limitation, actually serves to streamline the development process, ensuring that applications are built with scalability and maintainability in mind from the outset.

Drawing parallels to functional programming, as discussed in our blog post on functional programming in R, the discipline imposed by such paradigms often leads to more robust, testable, and reliable code. In the same vein, Shiny for Python's architectural design nudges developers towards cleaner code by structuring applications into discrete, reusable modules. Each module encapsulates a specific functionality, reducing interdependencies and making the codebase more manageable.

Moreover, Shiny for Python's emphasis on modularity aligns with the principles of good software design, where each component has a well-defined responsibility. This separation of concerns not only aids in debugging and testing but also facilitates collaboration among developers, as modules can be developed and tested in isolation before being integrated into larger applications.

In essence, Shiny for Python's architectural benefits are all about embedding a culture of software craftsmanship. By making it more challenging to write bad code, Shiny for Python ensures that developers are guided towards practices that will stand the test of time, much like the enduring principles of functional programming have shown. 

Wrapping Up: Why Shiny for Python Represents the Future

It's clear as day that Shiny for Python's emphasis on modularity, maintainability, and adherence to robust software design principles positions it not just as a contemporary solution, but as a forward-looking framework that's set to shape the future of dashboard development.

Shiny for Python's approach resonates with the ongoing shift in the tech industry towards sustainable and scalable software practices. By incorporating lessons from the past and aligning with modern programming paradigms, Shiny for Python is poised to be a key player in the realm of interactive web applications.

For those looking to leverage this cutting-edge technology, our team at Appsilon offers comprehensive services to meet a wide range of needs. Whether it's rapid dashboard development, full-stack engineering support (from setting up a Shiny server to UX optimization), or DevOps advisory for Posit products, we have the expertise and experience to help bring your projects to fruition. Our commitment to quality and innovation ensures that we're not just developers but partners in your journey towards leveraging the best of what technology has to offer.

If you're interested in exploring how Shiny for Python can transform your data visualization and dashboarding efforts, or if you need expert assistance in any aspect of dashboard development, don't hesitate to reach out to us at Appsilon. Together, we can create solutions that are not only efficient and effective but also future-proof, embodying the best practices that Shiny for Python champions.

Ready to deploy your Shiny for Python applications? Explore our Tapyr framework that helps you do this seamlessly.

Have questions or insights?

Engage with experts, share ideas and take your data journey to the next level!

Sign up for ShinyWeekly

Join 4,2k explorers and get the Shiny Weekly Newsletter into your mailbox
for the latest in R/Shiny and Data Science.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Explore Possibilities

Share Your Data Goals with Us

From advanced analytics to platform development and pharma consulting, we craft solutions tailored to your needs.

Talk to our Experts
shiny
shiny for python
python