Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/okld/streamlit-elements

Create a draggable and resizable dashboard in Streamlit, featuring Material UI widgets, Monaco editor (Visual Studio Code), Nivo charts, and more!
https://github.com/okld/streamlit-elements

Last synced: about 1 month ago
JSON representation

Create a draggable and resizable dashboard in Streamlit, featuring Material UI widgets, Monaco editor (Visual Studio Code), Nivo charts, and more!

Awesome Lists containing this project

README

        

✨ Streamlit Elements   [![GitHub][github_badge]][github_link] [![PyPI][pypi_badge]][pypi_link]
=====================

Create a draggable and resizable dashboard in Streamlit, featuring Material UI widgets, Monaco editor (Visual Studio Code), Nivo charts, and more!

Demo
----

[![Open in Streamlit][share_badge]][share_link]

[![Preview][share_video]][share_link]

[share_badge]: https://static.streamlit.io/badges/streamlit_badge_black_white.svg
[share_link]: https://share.streamlit.io/okld/streamlit-gallery/main?p=elements
[share_video]: https://github.com/okld/streamlit-elements/raw/main/demo.gif

[github_badge]: https://badgen.net/badge/icon/GitHub?icon=github&color=black&label
[github_link]: https://github.com/okld/streamlit-elements

[pypi_badge]: https://badgen.net/pypi/v/streamlit-elements?icon=pypi&color=black&label
[pypi_link]: https://pypi.org/project/streamlit-elements

Getting started
---------------

### 1. Introduction

Streamlit Elements is a component that gives you the tools to compose beautiful applications with Material UI widgets, Monaco, Nivo charts, and more.
It also includes a feature to create draggable and resizable dashboards.

#### Installation

```sh
pip install streamlit-elements==0.1.*
```

**Caution**: It is recommended to pin the version to `0.1.*`. Future versions might introduce breaking API changes.

#### Available elements and objects

Here is a list of elements and objects you can import in your app:

Element | Description
:--------:|:-----------
elements | Create a frame where elements will be displayed.
dashboard | Build a draggable and resizable dashboard.
mui | Material UI (MUI) widgets and icons.
html | HTML objects.
editor | Monaco code and diff editor that powers Visual Studio Code.
nivo | Nivo chart library.
media | Media player.
sync | Callback to synchronize Streamlit's session state with elements events data.
lazy | Defer a callback call until another non-lazy callback is called.

#### Caution

- A few Material UI widgets may not work as expected (ie. modal dialogs and snackbars).
- Using many element frames can significantly impact your app's performance. Try to gather elements in few frames at most.

---

### 2. Display elements

#### 2.1. Create an element with a child

```python
# First, import the elements you need

from streamlit_elements import elements, mui, html

# Create a frame where Elements widgets will be displayed.
#
# Elements widgets will not render outside of this frame.
# Native Streamlit widgets will not render inside this frame.
#
# elements() takes a key as parameter.
# This key can't be reused by another frame or Streamlit widget.

with elements("new_element"):

# Let's create a Typography element with "Hello world" as children.
# The first step is to check Typography's documentation on MUI:
# https://mui.com/components/typography/
#
# Here is how you would write it in React JSX:
#
#
# Hello world
#

mui.Typography("Hello world")
```
- MUI Typography: https://mui.com/components/typography/

---

#### 2.2. Create an element with multiple children

```python
with elements("multiple_children"):

# You have access to Material UI icons using: mui.icon.IconNameHere
#
# Multiple children can be added in a single element.
#
#
#
#
# Hello world
#

mui.Button(
mui.icon.EmojiPeople,
mui.icon.DoubleArrow,
"Button with multiple children"
)

# You can also add children to an element using a 'with' statement.
#
#
#
#
#
# Hello world
#
#

with mui.Button:
mui.icon.EmojiPeople()
mui.icon.DoubleArrow()
mui.Typography("Button with multiple children")
```
- MUI button: https://mui.com/components/buttons/
- MUI icons: https://mui.com/components/material-icons/

---

#### 2.3. Create an element with nested children

```python
with elements("nested_children"):

# You can nest children using multiple 'with' statements.
#
#
#
#

Hello world


#

Goodbye world


#
#

with mui.Paper:
with mui.Typography:
html.p("Hello world")
html.p("Goodbye world")
```
- MUI paper: https://mui.com/components/paper/

---

#### 2.4. Add properties to an element

```python
with elements("properties"):

# You can add properties to elements with named parameters.
#
# To find all available parameters for a given element, you can
# refer to its related documentation on mui.com for MUI widgets,
# on https://microsoft.github.io/monaco-editor/ for Monaco editor,
# and so on.
#
#
#
#

with mui.Paper(elevation=3, variant="outlined", square=True):
mui.TextField(
label="My text input",
defaultValue="Type here",
variant="outlined",
)

# If you must pass a parameter which is also a Python keyword, you can append an
# underscore to avoid a syntax error.
#
#

mui.Collapse(in_=True)

# mui.collapse(in=True)
# > Syntax error: 'in' is a Python keyword:
```
- MUI text field: https://mui.com/components/text-fields/

---

#### 2.5. Apply custom CSS styles

##### 2.5.1. Material UI elements

```python
with elements("style_mui_sx"):

# For Material UI elements, use the 'sx' property.
#
#
# Some text in a styled box
#

mui.Box(
"Some text in a styled box",
sx={
"bgcolor": "background.paper",
"boxShadow": 1,
"borderRadius": 2,
"p": 2,
"minWidth": 300,
}
)
```
- MUI's **sx** property: https://mui.com/system/the-sx-prop/

##### 2.5.2. Other elements

```python
with elements("style_elements_css"):

# For any other element, use the 'css' property.
#
#


# This has a hotpink background
#

html.div(
"This has a hotpink background",
css={
"backgroundColor": "hotpink",
"&:hover": {
"color": "lightgreen"
}
}
)
```
- Emotion's **css** property: https://emotion.sh/docs/css-prop#object-styles

---

### 3. Callbacks

#### 3.1. Retrieve an element's data

```python
import streamlit as st

with elements("callbacks_retrieve_data"):

# Some element allows executing a callback on specific event.
#
# const [name, setName] = React.useState("")
# const handleChange = (event) => {
# // You can see here that a text field value
# // is stored in event.target.value
# setName(event.target.value)
# }
#
#

# Initialize a new item in session state called "my_text"
if "my_text" not in st.session_state:
st.session_state.my_text = ""

# When text field changes, this function will be called.
# To know which parameters are passed to the callback,
# you can refer to the element's documentation.
def handle_change(event):
st.session_state.my_text = event.target.value

# Here we display what we have typed in our text field
mui.Typography(st.session_state.my_text)

# And here we give our 'handle_change' callback to the 'onChange'
# property of the text field.
mui.TextField(label="Input some text here", onChange=handle_change)
```
- MUI text field event: https://mui.com/components/text-fields/#uncontrolled-vs-controlled
- MUI text field API: https://mui.com/api/text-field/

---

#### 3.2. Synchronize a session state item with an element event using sync()

```python
with elements("callbacks_sync"):

# If you just want to store callback parameters into Streamlit's session state
# like above, you can also use the special function sync().
#
# When an onChange event occurs, the callback is called with an event data object
# as argument. In the example below, we are synchronizing that event data object with
# the session state item 'my_event'.
#
# If an event passes more than one parameter, you can synchronize as many session state item
# as needed like so:
# >>> sync("my_first_param", "my_second_param")
#
# If you want to ignore the first parameter of an event but keep synchronizing the second,
# pass None to sync:
# >>> sync(None, "second_parameter_to_keep")

from streamlit_elements import sync

if "my_event" not in st.session_state:
st.session_state.my_event = None

if st.session_state.my_event is not None:
text = st.session_state.my_event.target.value
else:
text = ""

mui.Typography(text)
mui.TextField(label="Input some text here", onChange=sync("my_event"))
```

---

#### 3.3. Avoid too many reloads with lazy()

```python
with elements("callbacks_lazy"):

# With the two first examples, each time you input a letter into the text field,
# the callback is invoked but the whole app is reloaded as well.
#
# To avoid reloading the whole app on every input, you can wrap your callback with
# lazy(). This will defer the callback invocation until another non-lazy callback
# is invoked. This can be useful to implement forms.

from streamlit_elements import lazy

if "first_name" not in st.session_state:
st.session_state.first_name = None
st.session_state.last_name = None

if st.session_state.first_name is not None:
first_name = st.session_state.first_name.target.value
else:
first_name = "John"

if st.session_state.last_name is not None:
last_name = st.session_state.last_name.target.value
else:
last_name = "Doe"

def set_last_name(event):
st.session_state.last_name = event

# Display first name and last name
mui.Typography("Your first name: ", first_name)
mui.Typography("Your last name: ", last_name)

# Lazily synchronize onChange with first_name and last_name state.
# Inputting some text won't synchronize the value yet.
mui.TextField(label="First name", onChange=lazy(sync("first_name")))

# You can also pass regular python functions to lazy().
mui.TextField(label="Last name", onChange=lazy(set_last_name))

# Here we give a non-lazy callback to onClick using sync().
# We are not interested in getting onClick event data object,
# so we call sync() with no argument.
#
# You can use either sync() or a regular python function.
# As long as the callback is not wrapped with lazy(), its invocation will
# also trigger every other defered callbacks.
mui.Button("Update first namd and last name", onClick=sync())
```

---

#### 3.4. Invoke a callback when a sequence is pressed using event.Hotkey()

```python
with elements("callbacks_hotkey"):

# Invoke a callback when a specific hotkey sequence is pressed.
#
# For more information regarding sequences syntax and supported keys,
# go to Mousetrap's project page linked below.
#
# /!\ Hotkeys work if you don't have focus on Streamlit Elements's frame /!\
# /!\ As with other callbacks, this reruns the whole app /!\

from streamlit_elements import event

def hotkey_pressed():
print("Hotkey pressed")

event.Hotkey("g", hotkey_pressed)

# If you want your hotkey to work even in text fields, set bind_inputs to True.
event.Hotkey("h", hotkey_pressed, bindInputs=True)
mui.TextField(label="Try pressing 'h' while typing some text here.")

# If you want to override default hotkeys (ie. ctrl+f to search in page),
# set overrideDefault to True.
event.Hotkey("ctrl+f", hotkey_pressed, overrideDefault=True)
```
- Mousetrap: https://craig.is/killing/mice
- Github page: https://github.com/ccampbell/mousetrap

---

#### 3.5. Invoke a callback periodically using event.Interval()

```python
with elements("callbacks_interval"):

# Invoke a callback every n seconds.
#
# /!\ As with other callbacks, this reruns the whole app /!\

def call_every_second():
print("Hello world")

event.Interval(1, call_every_second)
```

---

### 4. Draggable and resizable dashboard

```python
with elements("dashboard"):

# You can create a draggable and resizable dashboard using
# any element available in Streamlit Elements.

from streamlit_elements import dashboard

# First, build a default layout for every element you want to include in your dashboard

layout = [
# Parameters: element_identifier, x_pos, y_pos, width, height, [item properties...]
dashboard.Item("first_item", 0, 0, 2, 2),
dashboard.Item("second_item", 2, 0, 2, 2, isDraggable=False, moved=False),
dashboard.Item("third_item", 0, 2, 1, 1, isResizable=False),
]

# Next, create a dashboard layout using the 'with' syntax. It takes the layout
# as first parameter, plus additional properties you can find in the GitHub links below.

with dashboard.Grid(layout):
mui.Paper("First item", key="first_item")
mui.Paper("Second item (cannot drag)", key="second_item")
mui.Paper("Third item (cannot resize)", key="third_item")

# If you want to retrieve updated layout values as the user move or resize dashboard items,
# you can pass a callback to the onLayoutChange event parameter.

def handle_layout_change(updated_layout):
# You can save the layout in a file, or do anything you want with it.
# You can pass it back to dashboard.Grid() if you want to restore a saved layout.
print(updated_layout)

with dashboard.Grid(layout, onLayoutChange=handle_layout_change):
mui.Paper("First item", key="first_item")
mui.Paper("Second item (cannot drag)", key="second_item")
mui.Paper("Third item (cannot resize)", key="third_item")
```
- Dashboard item properties: https://github.com/react-grid-layout/react-grid-layout#grid-item-props
- Dashboard grid properties (Streamlit Elements uses the Responsive grid layout):
- https://github.com/react-grid-layout/react-grid-layout#grid-layout-props
- https://github.com/react-grid-layout/react-grid-layout#responsive-grid-layout-props

---

### 5. Other third-party elements

#### 5.1. Monaco code and diff editor

```python
with elements("monaco_editors"):

# Streamlit Elements embeds Monaco code and diff editor that powers Visual Studio Code.
# You can configure editor's behavior and features with the 'options' parameter.
#
# Streamlit Elements uses an unofficial React implementation (GitHub links below for
# documentation).

from streamlit_elements import editor

if "content" not in st.session_state:
st.session_state.content = "Default value"

mui.Typography("Content: ", st.session_state.content)

def update_content(value):
st.session_state.content = value

editor.Monaco(
height=300,
defaultValue=st.session_state.content,
onChange=lazy(update_content)
)

mui.Button("Update content", onClick=sync())

editor.MonacoDiff(
original="Happy Streamlit-ing!",
modified="Happy Streamlit-in' with Elements!",
height=300,
)
```
- Monaco examples and properties: https://github.com/suren-atoyan/monaco-react
- Code editor options: https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html
- Diff editor options: https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneDiffEditorConstructionOptions.html
- Monaco project page: https://microsoft.github.io/monaco-editor/

---

#### 5.2. Nivo charts

```python
with elements("nivo_charts"):

# Streamlit Elements includes 45 dataviz components powered by Nivo.

from streamlit_elements import nivo

DATA = [
{ "taste": "fruity", "chardonay": 93, "carmenere": 61, "syrah": 114 },
{ "taste": "bitter", "chardonay": 91, "carmenere": 37, "syrah": 72 },
{ "taste": "heavy", "chardonay": 56, "carmenere": 95, "syrah": 99 },
{ "taste": "strong", "chardonay": 64, "carmenere": 90, "syrah": 30 },
{ "taste": "sunny", "chardonay": 119, "carmenere": 94, "syrah": 103 },
]

with mui.Box(sx={"height": 500}):
nivo.Radar(
data=DATA,
keys=[ "chardonay", "carmenere", "syrah" ],
indexBy="taste",
valueFormat=">-.2f",
margin={ "top": 70, "right": 80, "bottom": 40, "left": 80 },
borderColor={ "from": "color" },
gridLabelOffset=36,
dotSize=10,
dotColor={ "theme": "background" },
dotBorderWidth=2,
motionConfig="wobbly",
legends=[
{
"anchor": "top-left",
"direction": "column",
"translateX": -50,
"translateY": -40,
"itemWidth": 80,
"itemHeight": 20,
"itemTextColor": "#999",
"symbolSize": 12,
"symbolShape": "circle",
"effects": [
{
"on": "hover",
"style": {
"itemTextColor": "#000"
}
}
]
}
],
theme={
"background": "#FFFFFF",
"textColor": "#31333F",
"tooltip": {
"container": {
"background": "#FFFFFF",
"color": "#31333F",
}
}
}
)
```
- Nivo charts: https://nivo.rocks/
- Github page: https://github.com/plouc/nivo

---

#### 5.3. Media player

```python
with elements("media_player"):

# Play video from many third-party sources: YouTube, Facebook, Twitch,
# SoundCloud, Streamable, Vimeo, Wistia, Mixcloud, DailyMotion and Kaltura.
#
# This element is powered by ReactPlayer (GitHub link below).

from streamlit_elements import media

media.Player(url="https://www.youtube.com/watch?v=iik25wqIuFo", controls=True)
```
- ReactPlayer properties: https://github.com/cookpete/react-player#props