✨ Streamlit Elements - Build draggable and resizable dashboards with Material UI, Nivo charts, and more!

Hello everyone :wave:

I’m happy to announce the release of Streamlit Elements :tada:

This component will allow you to create beautiful applications with Material UI, Nivo charts, with hotkeys and callbacks support, and with even more other widgets and features.

In addition, you will be able to create draggable and resizable dashboards for your Streamlit apps.

Open in Streamlit

:checkered_flag: Installation and usage

Everything from installation to usage is described there :point_down:

:calendar: Day 27 challenge of #30DaysOfStreamlit

#30DaysOfStreamlit is a coding challenge organized by Streamlit to help you get started on creating and sharing your applications. It covers the basics from setting up your coding environment and building your first app, up to advanced features such as caching and session state.

Day 27 challenge was about Streamlit Elements, which featured how to build a dashboard with multiple elements.

If you have missed it, you can check this Twitter thread for more information, and you can check out the #30DaysOfStreamlit app.

:thinking: Any question?

If you have any question, feel free to ask in this topic below.

Happy Streamlit-ing :balloon:

26 Likes

IT’S FINALLY PUBLIC :smiley: let’s goooooo

also don’t forget to Streamlit Components - Community Tracker :stuck_out_tongue:

Fanilo

7 Likes

Yes finally haha :smiley: Streamlit Elements added to your community tracker :+1:

3 Likes

Totally awesome. New superpowers for Streamliterati! :yum:

4 Likes

Truly awesome, streamlit on steroids!

2 Likes

Great to see this

1 Like

Muito animado!!

1 Like

Amazing Job ! I have a question about dashboard.Grid and image from bytes.

I’m currently building an object detect app with Streamlit but after seing your job I decided to implement a Version with a Dashboard style (elements dashboard.Grid) .

I face some issue to display an image from bytes using dashboard.Grid.

  • I tried to use st.image but as you can’t specify a key its not working.
  • I also tried with media card from mui without any success !
1 Like

Hello @HadarakDev, welcome to the forum!

  • I tried to use st.image but as you can’t specify a key its not working.

Unfortunately, you cannot mix Streamlit native widgets (ie. st.image) with Streamlit Elements.

  • I also tried with media card from mui without any success !

Would you mind sharing your code? The component is not as easy-to-use as it seems, and your code could help me understand if there is some unclear aspects in my documentation, or even give me ideas to improve the API.

As to how to do it, here’s a demo with the source code below:

https://imgur.com/a/g7uwj1Y

import requests
import streamlit as st
from base64 import b64encode
from streamlit_elements import elements, dashboard, html

st.set_page_config(layout="wide")

# Some random image URL.
images_url = [
    "https://unsplash.com/photos/1CsaVdwfIew/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjUxNTE3OTQx&force=true&w=1920",
    "https://unsplash.com/photos/eHlVZcSrjfg/download?force=true&w=1920",
    "https://unsplash.com/photos/CSs8aiN_LkI/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjUxNTE2ODM1&force=true&w=1920",
    "https://unsplash.com/photos/GJ8ZQV7eGmU/download?force=true&w=1920",
]

# Download these images and get their bytes.
images_bytes = [requests.get(url).content for url in images_url]

# Encode these bytes to base 64.
images_b64 = [b64encode(bytes).decode() for bytes in images_bytes]

# Initialize a layout for our dashboard.
# It's gonna be a 2x2 grid, with each element being of height 3 and width 6 out of 12.
layout = [
    dashboard.Item("image0", 0, 0, 6, 3),
    dashboard.Item("image1", 6, 0, 6, 3),
    dashboard.Item("image2", 0, 3, 6, 3),
    dashboard.Item("image3", 6, 3, 6, 3),
]

with elements("image_grid"):
    with dashboard.Grid(layout):
        # We iterate over our images encoded as base64.
        # enumerate() will return the item's index i from 0 to 3, so I can generate
        # dashboard layout keys from "image0" to "image3".
        for i, b64 in enumerate(images_b64):
            html.img(
                # We pass our base 64 to <img src=...></img> to display our image.
                # See: https://stackoverflow.com/a/8499716
                src=f"data:image/png;base64,{b64}",
                # A simple CSS style to avoid image distortion on resize.
                css={"object-fit": "cover"},
                # We set the key to bind our image to a dashboard item.
                key=f"image{i}",
            )
2 Likes

Thank’s for you help.

The part of my code which tries to use dashboard is the one above.
I was just doing a single element dashboard to make my image resizable.

For the media card from mui I tried to pass both raw bytes to src and pass using the image/png.

I will try to use html.img and wrap it inside a Card or a Paper.

import streamlit as st
from streamlit_elements import elements, mui, dashboard

layout = [
    # Parameters: element_identifier, x_pos, y_pos, width, height, [item properties...]
    dashboard.Item("loaded_img", 0, 0, 10, 2, isResizable=True)
]


def show_image():
	with elements("image"):
		with dashboard.Grid(layout):
			mui.CardMedia(component="img" , src=(f"data:image/png,{st.session_state['img']}"))
			
if __name__ == "__main__":
    images_files = st.sidebar.file_uploader(
        "Import Images to Review",
        type=[".tiff", ".tif"]
    )
	
	if images_files:
		process_image() ## 1- This function read the image
					    ##	   2- Apply some transformations
				     	##	  3- Store the results in st.state

        show_image()

1 Like

Using CardMedia like you did is fine, however I think you should put it inside a Card element. Also, make sure st.session_state['img'] returns bytes encoded in base 64.

In case it doesn’t work, you may find some error in your browser developer console (F11).

hot hot hot :fire:. Finally you got a release out, @olkd! Well done.
Looking forward to officially use cards in my apps.

Best regards
Chris

1 Like

@okld - Hi, I’m not familiar with MUI and have looked at the online docs. Can you please post an example of how this image grid example can be extended to have image transitions using mui.Slide, mui.Fade, mui.Grow, etc.? And relate that to the docs which show React/JS examples. Thanks!

Hey @asehmi,

Here’s a demo of two slideshow functions. The first one uses swipeable views to allow mobile users to swipe images left and right (I suppose you’d find it interesting for your usecase), and the second slideshow function uses the transitions you mentioned. Source code below.

import streamlit as st
from streamlit_elements import elements, mui, html, sync

IMAGES = [
    "https://unsplash.com/photos/GJ8ZQV7eGmU/download?force=true&w=1920",
    "https://unsplash.com/photos/eHlVZcSrjfg/download?force=true&w=1920",
    "https://unsplash.com/photos/zVhYcSjd7-Q/download?force=true&w=1920",
    "https://unsplash.com/photos/S5uIITJDq8Y/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjUyOTAzMzAz&force=true&w=1920",
    "https://unsplash.com/photos/E4bmf8BtIBE/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjUyOTEzMzAw&force=true&w=1920",
]


def slideshow_swipeable(images):
    # Generate a session state key based on images.
    key = f"slideshow_swipeable_{str(images).encode().hex()}"

    # Initialize the default slideshow index.
    if key not in st.session_state:
        st.session_state[key] = 0

    # Get the current slideshow index.
    index = st.session_state[key]

    # Create a new elements frame.
    with elements(f"frame_{key}"):

        # Use mui.Stack to vertically display the slideshow and the pagination centered.
        # https://mui.com/material-ui/react-stack/#usage
        with mui.Stack(spacing=2, alignItems="center"):

            # Create a swipeable view that updates st.session_state[key] thanks to sync().
            # It also sets the index so that changing the pagination (see below) will also
            # update the swipeable view.
            # https://mui.com/material-ui/react-tabs/#full-width
            # https://react-swipeable-views.com/demos/demos/
            with mui.SwipeableViews(index=index, resistance=True, onChangeIndex=sync(key)):
                for image in images:
                    html.img(src=image, css={"width": "100%"})

            # Create a handler for mui.Pagination.
            # https://mui.com/material-ui/react-pagination/#controlled-pagination
            def handle_change(event, value):
                # Pagination starts at 1, but our index starts at 0, explaining the '-1'.
                st.session_state[key] = value-1

            # Display the pagination.
            # As the index value can also be updated by the swipeable view, we explicitely
            # set the page value to index+1 (page value starts at 1).
            # https://mui.com/material-ui/react-pagination/#controlled-pagination
            mui.Pagination(page=index+1, count=len(images), color="primary", onChange=handle_change)


def slideshow_transition(images, transition):
    # Generate a session state key based on images.
    key = f"slideshow_transition_{str(images).encode().hex()}"

    # Initialize the default slideshow page.
    if key not in st.session_state:
        st.session_state[key] = 1

    # Get the current slideshow index.
    page = st.session_state[key]

    # Create a new elements frame.
    with elements(f"frame_{key}"):

        # Use mui.Stack to vertically display the slideshow and the pagination centered.
        # https://mui.com/material-ui/react-stack/#usage
        with mui.Stack(spacing=2, alignItems="center"):

            # Create a CSS grid.
            # All slides will be displayed in the same column and row, they will overlap.
            # https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout
            with html.div(css={"display": "grid", "gridTemplateColumns": "1fr", "overflow": "hidden"}):

                # Iterate over images.
                # Generate an index/page that starts at 1 to check which image is selected.
                for page, image in enumerate(images, 1):
                    selected = (st.session_state[key] == page)

                    # Wrap the image in a transition.
                    # mui.Grow and mui.Fade takes a 'in' property, however 'in' is also
                    # a python keyword you cannot use as argument name. To bypass this
                    # issue, you can just append an underscore.
                    # https://mui.com/material-ui/transitions/
                    with mui[transition](in_=selected):
                        # Display the image in the first column and row.
                        html.img(src=image, css={
                            "gridRow": 1,
                            "gridColumn": 1,
                            "width": "100%",
                        })

            # Display the pagination.
            # Synchronize onChange callback's second parameter with st.session_state[key].
            # Ignore the first parameter by using None as first argument in sync().
            # https://mui.com/material-ui/react-pagination/#controlled-pagination
            # https://mui.com/material-ui/api/pagination/#props (onChange)
            mui.Pagination(count=len(images), color="primary", onChange=sync(None, key))



st.title("Streamlit Elements Slideshow")

st.subheader("Swipeable slideshow")
slideshow_swipeable(IMAGES)

st.subheader("Slideshow with transitions")
transition = st.radio("Select your transition", ["Collapse", "Fade", "Grow", "Slide", "Zoom"])
slideshow_transition(IMAGES, transition)
2 Likes

@okld - that’s fantabulous! I’ll be looking at this immediately, and learn a trick or two.

Many thanks for taking the time to put it together so promptly.

Best wishes,
Arvindra

1 Like

P.S. I see the solution you used to get around reserved keywords! :thinking: That stumped me when I tried to use mui.

Hi @okld :smile: THIS IS AMAZING!

Im trying to use it with plotly graph but i really don’t know how to drag it (there are 4 cards each on has a title, a subtitle and a graph) bc of the examples are using custom classes. Any advice or guide?

Thanks in advance!

code: GitHub - angelicaba23/icfes-vive-digital

HI All

I am trying to do a dynamic dash grid with mui Cards components. I am doing something wrong and cannot figure it out Could you help?

code:

def mui_card(result):
    
    
    layout=[]
    for index, row in result.iterrows():
        layout.append(dashboard.Item(
            row['printer_serial'],
            max(0,round(index*2 -1)), 
            max(0,round(index*2 -1)),
            1,
            1, 
            isResizable=False, 
            isDraggable=False, moved=False))
    print(layout)
    with elements('card'):
        with dashboard.Grid(layout, justify="center"):
            for index, row in result.iterrows():
            
                with mui.Card(sx={"maxWidth": 345}):
                    mui.CardMedia(
                        component="img", 
                        image="...",
                        # height="140",
                        # width="140"
                        )
                    mui.CardHeader(title=row['printer_serial'])

error:
streamlit_elements.core.exceptions.ElementsFrontendError: In elements frame ‘streamlit_elements.core.frame.elements_frame.card’: Cannot read properties of undefined (reading ‘y’)

@okld im iterating over a list and creating mui cards as shown below:

def create_section_email_previews(videos, content_container):
    for index, video in enumerate(videos):
        val = index
        # create mui card
        mui.Card(
            mui.CardHeader(
                title= mui.Typography( video['videoTitle'], variant="h6", sx={"textAlign": "center"}),
            ),
            mui.CardMedia(
                image=video['thumbnailUrl'],
                component="img",
                sx={"maxHeight": "100%", "maxWidth": "100%", "width": "unset", },
                width=None
            ),
            mui.CardActions(
                mui.Button("View Email", variant="contained", color="primary", onClick=lambda: create_email_preview_page(content_container, tab_name=videos[index]['videoTitle'], tab_data=videos[index], videos=videos, index=val))                            
                ),
            sx={"width": "30%", 'display': 'flex', 'flexDirection': 'column', 'justifyContent': 'center', 'alignItems': 'center', 'margin': '1rem 0', 'paddingBottom': '1rem'},
        )

My issue comes from the onClick handler on the mui.Button. I pass in the index and check the index inside of the handler and for some reason, the index is ALWAYS the length of the list minus one. How can I fix this issue?

3 Likes

Hi, @okld
Does streamlit-elements support nivo.Geo usage?
I tried to use nivo.Geo in dashboard, it report error like this:


I also tried to use nivo.Choropleth in dashboard, it report error like this:

So how to embed geo map in streamlit-elements app?
Thank you.