Automatic slideshow

Summary

I have an app running in my company that shows a kpi number in a screen.

I want to know if it is possible to make something like a carousel in bootstrap that every 5 seconds makes a transition and shows another kpi.

Hi @kiko249 could you share with us about the things you’ve already tried?

  • tried using slider with time module
    — got problems with reset to the beggining
    — got problems with duplicate widget api error

  • tried extra components switch pages
    — dont have the effect expected as it kinda flashes when switching page

  • tried other custom build components but none of them worked as expected.

I want to have something more powepoint-like transitions

Any suggestions??

Best solution i found so far is the elements module but so far i wasn’t able to implement an automatic change of elements:

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)


if __name__ == '__main__':
    st.title("Streamlit Elements Slideshow")

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

If you want to have a simple slide show you can always use streamlit.components.v1 and pass your own html code. For example:

gallery


import streamlit as st
import streamlit.components.v1 as components

components.html(
    """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* {box-sizing: border-box;}
body {font-family: Verdana, sans-serif;}
.mySlides {display: none;}
img {vertical-align: middle;}

/* Slideshow container */
.slideshow-container {
  max-width: 1000px;
  position: relative;
  margin: auto;
}

/* Caption text */
.text {
  color: #f2f2f2;
  font-size: 15px;
  padding: 8px 12px;
  position: absolute;
  bottom: 8px;
  width: 100%;
  text-align: center;
}

/* Number text (1/3 etc) */
.numbertext {
  color: #f2f2f2;
  font-size: 12px;
  padding: 8px 12px;
  position: absolute;
  top: 0;
}

/* The dots/bullets/indicators */
.dot {
  height: 15px;
  width: 15px;
  margin: 0 2px;
  background-color: #bbb;
  border-radius: 50%;
  display: inline-block;
  transition: background-color 0.6s ease;
}

.active {
  background-color: #717171;
}

/* Fading animation */
.fade {
  animation-name: fade;
  animation-duration: 1.5s;
}

@keyframes fade {
  from {opacity: .4} 
  to {opacity: 1}
}

/* On smaller screens, decrease text size */
@media only screen and (max-width: 300px) {
  .text {font-size: 11px}
}
</style>
</head>
<body>

<h2>Automatic Slideshow</h2>
<p>Change image every 2 seconds:</p>

<div class="slideshow-container">

<div class="mySlides fade">
  <div class="numbertext">1 / 3</div>
  <img src="https://unsplash.com/photos/GJ8ZQV7eGmU/download?force=true&w=1920" style="width:100%">
  <div class="text">Caption Text</div>
</div>

<div class="mySlides fade">
  <div class="numbertext">2 / 3</div>
  <img src="https://unsplash.com/photos/eHlVZcSrjfg/download?force=true&w=1920" style="width:100%">
  <div class="text">Caption Two</div>
</div>

<div class="mySlides fade">
  <div class="numbertext">3 / 3</div>
  <img src="https://unsplash.com/photos/zVhYcSjd7-Q/download?force=true&w=1920" style="width:100%">
  <div class="text">Caption Three</div>
</div>

</div>
<br>

<div style="text-align:center">
  <span class="dot"></span> 
  <span class="dot"></span> 
  <span class="dot"></span> 
</div>

<script>
let slideIndex = 0;
showSlides();

function showSlides() {
  let i;
  let slides = document.getElementsByClassName("mySlides");
  let dots = document.getElementsByClassName("dot");
  for (i = 0; i < slides.length; i++) {
    slides[i].style.display = "none";  
  }
  slideIndex++;
  if (slideIndex > slides.length) {slideIndex = 1}    
  for (i = 0; i < dots.length; i++) {
    dots[i].className = dots[i].className.replace(" active", "");
  }
  slides[slideIndex-1].style.display = "block";  
  dots[slideIndex-1].className += " active";
  setTimeout(showSlides, 2000); // Change image every 2 seconds
}
</script>

</body>
</html> 

    """,
    height=600,
)

2 Likes

that works for me thank you!

1 Like

Hi @kiko249 I have implemented a component which should do exactly that. You can check it out here, hope this helps.

Hello Tomas

I tried your component but i’m getting executing the code posted on pypi
TypeError: unsupported operand type(s) for |: ‘type’ and ‘NoneType’

I’m running streamlit 1.23 and python 3.9

Hi Kiko,
Do you have a code snippet that I can try to reproduce your error?

Hi

I get the error running the code example provided in:

I think this had to do with a Python version compatibility issue. I have made a patch (i.e. version 0.0.4) which should solve the issue.
Could you please confirm?

Hello

Yes now it is working, good job
and thank you very much :slight_smile:

1 Like

Hey @kiko249 and @ThomasBouamoud,
Just wondering if mui.pagination works for images stored locally as well?
I mean to slideshow locally stored images in same format.

Hi @labhayl , the Carousel custom component does not currently support local images but I’m working on fixing this. It should be solved by the end of the week.

1 Like

Great Solution. Thanks for this.
Could you please help with following issues:

  1. I replaced the image path with “Images/myImage.png”. But the image is not displayed. The Images folder is there. And the file also exists.

  2. Dots are not displayed if used st.set_page_config(layout=“wide”)

Got it fixed here.

Thanks.

1 Like

Thanks for working on this. After pip install streamlit-carousel, which installed v1.1.0, I have this error. Any idea how to resolve it?

ImportError: cannot import name 'NotRequired' from 'typing'

Edit: Downgraded to v1.0.0 works.

Thanks

Hi @lavint , that should be fixed in version 1.1.2