Enforcing square ui.card elements that contain matplotlib figures

I'm having problems controlling the aspect ratio of ui.card elements in Shiny Express. The cards are intended to contain data generated by matplotlib and I'd like to enforce a square shape.

Example code to demonstrate:

from shiny.express import render, ui
import matplotlib.pyplot as plt

def donutchart(sizes):
    # Ensure plot is square
    fig, ax = plt.subplots(figsize=(1,1))

    # Color whole plot canvas to see "true" size vs e.g. white background
    fig.patch.set_facecolor('xkcd:mint green')

    props = { 'width': 0.5, 'linewidth': 1, 'edgecolor': 'white' }
    ax.pie(sizes, wedgeprops=props)

    # Print size of plot in pixels, to check the results are square
    print( fig.get_size_inches() * fig.dpi )

with ui.layout_column_wrap(fill=False):
    # This use of 'style' to set the aspect ratio does not work.
    with ui.card( style='aspect-ratio: 1/1' ):
        @render.plot
        def plot1():
            donutchart( [1,1,1,1] )

    with ui.card():
        @render.plot
        def plot2():
            donutchart( [1,2,3,4] )

    with ui.card():
        @render.plot
        def plot3():
            donutchart( [1,2,4,8] )

As you can see, the example code just sets up three ui.card items inside a ui.layout_column_wrap element. The matplotlib code generate a plots and prints out the size of the figure (in pixels) to check the specific dimensions of the matplotlib output to make sure they don't force some sort of vertical resize of the ui.card elements.

Example console output:

$ shiny run tmp.py 
INFO:     Started server process [24523]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:59032 - "GET / HTTP/1.1" 200 OK
INFO:     ('127.0.0.1', 59034) - "WebSocket /websocket/" [accepted]
INFO:     connection open
[200. 200.]
[200. 200.]
[200. 200.]

... so we can see that matplotlib is indeed respecting the square size when generating the figures (they are all 200x200 pixels).

However, resizing the browser window clearly shows that the aspect ratio set in the first ui.card is not respected (style='aspect-ratio: 1/1'). Furthermore, the green background of the matplotlib output plots fill the whole card - which they shouldn't, as the canvas of each plot is square (as shown in the console output - always 200x200 pixels).

Here is an example screenshot. I'd like to add more, but as a new user I can apparently only attach one.

The ui.card elements remain in the same non-square proportions as I resize the browser horizontally so all three cards are shown on the same line. The ui.card elements only become square after I resize the browser window on the x-axis beyond what the responsive flow seems to be able to entirely fill horizontally.

How can I enforce that the ui.card elements be square?

1 Like

I'll include additional screenshots in follow-up posts like this one to further illustrate the problem:

This shows the "correct" (i.e., square) aspect ratio after resizing the browser window as far as I can on the horizontal axis. Widths less than this result in "stretching" of the ui.card elements on the vertical axis, but the actual contents (i.e., the donut charts in the matplotlib figures) are always in the correct aspect ratio but with extra canvas added above and below the actual data.

You were on the right track. Adding a css rule

ui.tags.head(
    ui.tags.style("""
    .square-card {
        aspect-ratio: 1/1;
    }
    """)

and then class_="square-card" to the card seems to sort it. See: Shiny editor

1 Like

Thank you! Hopefully this approach will also address any future CSS styling problems I have! :+1:

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.