Custom Component: Material UI React Table

I am very interested in getting a nice table component. The st.write(dataframe) component is pretty good, but a true react table would be outstanding. Most of what I do is display tabular data. Could someone provide high level guidance how one would create a static table like this

and powered by a pandas dataframe behind the scenes?

Thank you.

2 Likes

Hi @jlarkin,

Your best bet for now is trying to play with the SelectableDataTable example which extracts your DataFrame from Python on the React side, and edit the return code in React to return a Material Table (check the source code for simple table) instead of the custom selectable datatable. I think this is the shortest path between what you want and what exists currently.

If you have no experience in React, it may be a good exercise to first follow the component-template tutorial. I also did a quick tutorial for integrating Plotly.js if you want to test. They teach mostly how to pass data back and forth between Python and React though, it won’t show you a lot about mapping the dataframe into a custom JS table, which the selectabledatatable example shows you.

if you’re only doing static rendering and don’t plan to interact with the table, maybe it’s possible to do it only with components.html actually hmmm…lemme see…

Fanilo

3 Likes

If you’re only doing static rendering (that is no interaction with the table and get back selected rows back to Streamlit) you can use components.html to run any HTML/JS code, so something like this is a good first start, maybe I’d use something like jinja2 or some string interpol to inject Python data into the HTML file :

This is a straight very rough copy of the CDN example project edited to include the Customized Table you mention in your OP. Not optimized for production !

app.py
import streamlit.components.v1 as components
import streamlit as st 

st.title("Hello Custom React Table")

with open('app.html') as f:
    data = f.read()
    components.html(data, height=800)
app.html
<!DOCTYPE html>
<html lang="en">

<head>
    <title>My page</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
    <script src="https://unpkg.com/react@latest/umd/react.development.js" crossorigin="anonymous"></script>
    <script src="https://unpkg.com/react-dom@latest/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js"
        crossorigin="anonymous"></script>
    <script src="https://unpkg.com/babel-standalone@latest/babel.min.js" crossorigin="anonymous"></script>
    <!-- Fonts to support Material Design -->
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
    <!-- Icons to support Material Design -->
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
</head>

<body>
    <div id="root"></div>
    <script type="text/babel">
        const {
            Button,
            CssBaseline,
            makeStyles,
            Paper,
            Table,
            TableBody,
            TableCell,
            TableContainer,
            TableHead,
            TableRow,
            withStyles
        } = MaterialUI;


        const StyledTableCell = withStyles((theme) => ({
            head: {
                backgroundColor: theme.palette.common.black,
                color: theme.palette.common.white,
            },
            body: {
                fontSize: 14,
            },
        }))(TableCell);

        const StyledTableRow = withStyles((theme) => ({
            root: {
                '&:nth-of-type(odd)': {
                    backgroundColor: theme.palette.action.hover,
                },
            },
        }))(TableRow);

        function createData(name, calories, fat, carbs, protein) {
            return { name, calories, fat, carbs, protein };
        }

        const rows = [
            createData('Frozen yoghurt', 159, 6.0, 24, 4.0),
            createData('Ice cream sandwich', 237, 9.0, 37, 4.3),
            createData('Eclair', 262, 16.0, 24, 6.0),
            createData('Cupcake', 305, 3.7, 67, 4.3),
            createData('Gingerbread', 356, 16.0, 49, 3.9),
        ];

        const useStyles = makeStyles({
            table: {
                minWidth: 700,
            },
        });

        const CustomTable = () => {
            const classes = useStyles();

            return (
                <TableContainer component={Paper}>
                    <Table className={classes.table} aria-label="customized table">
                        <TableHead>
                            <TableRow>
                                <StyledTableCell>Dessert (100g serving)</StyledTableCell>
                                <StyledTableCell align="right">Calories</StyledTableCell>
                                <StyledTableCell align="right">Fat&nbsp;(g)</StyledTableCell>
                                <StyledTableCell align="right">Carbs&nbsp;(g)</StyledTableCell>
                                <StyledTableCell align="right">Protein&nbsp;(g)</StyledTableCell>
                            </TableRow>
                        </TableHead>
                        <TableBody>
                            {rows.map((row) => (
                                <StyledTableRow key={row.name}>
                                    <StyledTableCell component="th" scope="row">
                                        {row.name}
                                    </StyledTableCell>
                                    <StyledTableCell align="right">{row.calories}</StyledTableCell>
                                    <StyledTableCell align="right">{row.fat}</StyledTableCell>
                                    <StyledTableCell align="right">{row.carbs}</StyledTableCell>
                                    <StyledTableCell align="right">{row.protein}</StyledTableCell>
                                </StyledTableRow>
                            ))}
                        </TableBody>
                    </Table>
                </TableContainer>
            );
        }

        const App = () => {
            return (
                <CustomTable />
            );
        }

        ReactDOM.render(
            <div>
                <CssBaseline />
                <App />
            </div>,
            document.querySelector('#root'),
        );
    </script>
</body>

</html>

5 Likes

Thank you @andfanilo! You are a national treasure! I love the plotly example as well.

I don’t have any experience in React (or Javascript, or HTML, or CSS …) but hacked up some ipyvuetify code at some point last year with success, so I sort of understand what’s going on here.

A full walkthru of Streamlit custom component creation (and production deployment) would be a great PyData Global conference presentation. Deadline August 2nd! :point_right: https://global.pydata.org/pages/cfp.html :pray: One of the great things about Streamlit today is that you need to know virtually nothing about front-end web dev but can still create beautiful pages for end users. I imagine I am in good company with other community members who would love to take advantage of custom components but lack some basic knowledge to do so.

Can you explain (or link to explainer) what’s going on with this?

_selectable_data_table = components.declare_component(
    "selectable_data_table", url="http://localhost:3001",
)

I’ve read thru the component docs you linked to. So this is a dev server? Where does it get hosted in production? How does one push to production (absent pushing to PyPI)?

3 Likes

:+1:

:thinking: hmmm interesting, we’ll look into it

Pointing the docs for this, the declare_component defines where Streamlit should fetch the React component, here on http://localhost:3001.
Notice the dev server that you start from my_component/frontend is configured to run on http://localhost:3001 :wink: .

The declare_component returns a function _selectable_data_table. Whenever you use that function, Streamlit calls the other dev frontend server on port 3001 for the component then renders it on your browser ! Any parameter you send like _selectable_data_table(name="Randy") will be available in the React component accessed from there, here in props.args.name.


For production, we don’t want that 2nd dev server for serving a frontend component. Instead we build and minify all frontend files with npm run build into a single bundle, and then instead of pointing to an URL we point to a path with the bundle on declare_component(path=build/). To publish on PyPi we deploy this small JS with the Python code, all together in the wheel file :slight_smile: that part is documented here


PS : shameless plug, if you’ve played with ipyvuetify maybe you’re familiar with Vue.js. I tried to build a Vue.js template for Component here. It’s not well documented and maybe bugs will pop up but maybe it’ll work for you


That’s too nice of you :upside_down_face:

1 Like

Thanks for sharing andfanilo!

These “React” tables look great!

I’ve yet to familiarise myself with React - do you think React could allow for some kind of on-the-fly table interactions (e.g. sorting or filtering?)

Thanks,
Charly

Yes! That’s key. It’s all done in the browser so no need to communicate back to Python. See the link in my initial post for examples or sorting, filtering, selecting, multi-page operations, etc.

2 Likes

Thanks for sharing Jonathan!

My good Lord!!!

Having some of the interactive tables listed here would be fantastic!!! :fire:

e.g.: the collapsible one! -> https://material-ui.com/components/tables/#collapsible-table

1 Like

I’m Olivier, a principal at Material-UI. We appreciate the interest in our table component. While the current version of the component focuses on the style, we are working on a new DataGrid API under https://github.com/mui-org/material-ui-x to solve all the advanced challenges around managing a lot of tabular data. This comes up in two flavors, an MIT license for the simplest features and an commercial one for the most advanced.

The demo with DevExpress (commercial license) gives an idea of what we are building.

  • What set of features do you need from the data table component?
3 Likes

An update. We have made significant progress with the data grid component of Material-UI. You can find the new component at https://material-ui.com/components/data-grid/. This is designed to handle a lot of tabular data effectively and efficiently. For this use case, the API of this <DataGrid> component is simpler to use than the one of the <Table>.

You can find an overview of the important features that are coming next in this page.

@jlarkin could you expand more on the problems you are trying to solve? What features do you wish to get from a “true react table”. Thanks!

As far as I’m aware there still isn’t any table component that allows a user to type in values and get these propagated back to the python layer, or am I missing something?

@fdubinski I haven’t tested it yet but you could try Ag-Grid component with input support (see demo where input in table is propagated into an Altair plot). Does that solve your pain ?

Fanilo

2 Likes

Hi @andfanilo Can u pls help me out in this?

I want to display a csv/excel file exactly as it is in a table format. The file contains some bullet points(with bullet marks also) in each cell which are separately written in new lines and the cells are wrapped.
Right now, when I am displaying that using table,
1)it is unable to show the points in separate lines, it is merging lines and is not properly readable as its getting messed up.
2)The bold dot marks to represent bullet point is being shown as ‘?’
3) Can I have the column names cells to be coloured ?

This is how the file looks like:

Table being displayed is:

Like in this image, file contains text in separate lines but its showing all together and it not properly readable

Thanks in advance!

Late to the party, but since I couldn’t really find what I needed on the Internet, I made a simple custom Streamlit component that did exactly what I aimed for - a replica of the Material UI React Table to display my DataFrame. Here is the demo app (in Chinese tho…).

The repository is here.

One can simply use it by installing it using pip install st-material-table and then use it in your app.py

app.py
from st_material_table import st_material_table
import streamlit as st
import pandas as pd

# create your dataframe

_ = st_material_table(df)
6 Likes

Hi,

any updates planned? Customization?

Thank you

If you are looking for good Material UI support in Streamlit, you’ll have to check :sparkles: Streamlit Elements - Build draggable and resizable dashboards with Material UI, Nivo charts, and more! - Streamlit Components - Streamlit

Have a nice day,
Failo