Streamlit-echarts

Hello all !

EDIT : source repo https://github.com/andfanilo/streamlit-echarts

Are there people familiar with echarts here ? I’m not very familiar with echarts myself and documentation can be daunting sometimes…so I’d like you to test the streamlit-echarts package.

# you need to be in an environment with Streamlit custom components
pip install streamlit-echarts
streamlit run app.py

with some sample files in the examples folder of the project https://github.com/andfanilo/streamlit-echarts/tree/master/examples. Following is a sample :

app.py
import pandas as pd
from random import randint
import streamlit as st

from streamlit_echarts import JsCode
from streamlit_echarts import st_echarts


def main():
    PAGES = {
        "Basic line chart": render_basic_line,
        "Basic area chart": render_basic_area,
        "Stacked area chart": render_stacked_area,
        "Mixed line and bar": render_mixed_line_bar,
        "Custom pie chart": render_custom_pie,
        "Effect scatter chart": render_effect_scatter,
        "Calendar heatmap": render_calendar_heatmap,
        "Basic treemap": render_treemap,
        "Datazoom": render_datazoom,
        "Dataset": render_dataset,
        "Map": render_map,
        "Click event": render_event,
    }

    st.title("Hello ECharts !")
    st.sidebar.header("Configuration")
    page = st.sidebar.selectbox("Choose an example", options=list(PAGES.keys()))
    PAGES[page]()


def render_basic_line():
    with st.echo("below"):
        options = {
            "xAxis": {
                "type": "category",
                "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
            },
            "yAxis": {"type": "value"},
            "series": [
                {"data": [820, 932, 901, 934, 1290, 1330, 1320], "type": "line"}
            ],
        }
        st_echarts(
            options=options, height="400px",
        )
        st_echarts(
            options=options, height="400px", theme="dark",
        )


def render_basic_area():
    with st.echo("below"):
        options = {
            "xAxis": {
                "type": "category",
                "boundaryGap": False,
                "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
            },
            "yAxis": {"type": "value"},
            "series": [
                {
                    "data": [820, 932, 901, 934, 1290, 1330, 1320],
                    "type": "line",
                    "areaStyle": {},
                }
            ],
        }
        st_echarts(options=options)


def render_stacked_area():
    with st.echo("below"):
        options = {
            "title": {"text": "堆叠区域图"},
            "tooltip": {
                "trigger": "axis",
                "axisPointer": {
                    "type": "cross",
                    "label": {"backgroundColor": "#6a7985"},
                },
            },
            "legend": {"data": ["邮件营销", "联盟广告", "视频广告", "直接访问", "搜索引擎"]},
            "toolbox": {"feature": {"saveAsImage": {}}},
            "grid": {"left": "3%", "right": "4%", "bottom": "3%", "containLabel": True},
            "xAxis": [
                {
                    "type": "category",
                    "boundaryGap": False,
                    "data": ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
                }
            ],
            "yAxis": [{"type": "value"}],
            "series": [
                {
                    "name": "邮件营销",
                    "type": "line",
                    "stack": "总量",
                    "areaStyle": {},
                    "data": [120, 132, 101, 134, 90, 230, 210],
                },
                {
                    "name": "联盟广告",
                    "type": "line",
                    "stack": "总量",
                    "areaStyle": {},
                    "data": [220, 182, 191, 234, 290, 330, 310],
                },
                {
                    "name": "视频广告",
                    "type": "line",
                    "stack": "总量",
                    "areaStyle": {},
                    "data": [150, 232, 201, 154, 190, 330, 410],
                },
                {
                    "name": "直接访问",
                    "type": "line",
                    "stack": "总量",
                    "areaStyle": {},
                    "data": [320, 332, 301, 334, 390, 330, 320],
                },
                {
                    "name": "搜索引擎",
                    "type": "line",
                    "stack": "总量",
                    "label": {"normal": {"show": True, "position": "top"}},
                    "areaStyle": {},
                    "data": [820, 932, 901, 934, 1290, 1330, 1320],
                },
            ],
        }
        st_echarts(options)


def render_mixed_line_bar():
    with st.echo("below"):
        options = {
            "tooltip": {
                "trigger": "axis",
                "axisPointer": {"type": "cross", "crossStyle": {"color": "#999"}},
            },
            "toolbox": {
                "feature": {
                    "dataView": {"show": True, "readOnly": False},
                    "magicType": {"show": True, "type": ["line", "bar"]},
                    "restore": {"show": True},
                    "saveAsImage": {"show": True},
                }
            },
            "legend": {"data": ["蒸发量", "降水量", "平均温度"]},
            "xAxis": [
                {
                    "type": "category",
                    "data": [
                        "1月",
                        "2月",
                        "3月",
                        "4月",
                        "5月",
                        "6月",
                        "7月",
                        "8月",
                        "9月",
                        "10月",
                        "11月",
                        "12月",
                    ],
                    "axisPointer": {"type": "shadow"},
                }
            ],
            "yAxis": [
                {
                    "type": "value",
                    "name": "水量",
                    "min": 0,
                    "max": 250,
                    "interval": 50,
                    "axisLabel": {"formatter": "{value} ml"},
                },
                {
                    "type": "value",
                    "name": "温度",
                    "min": 0,
                    "max": 25,
                    "interval": 5,
                    "axisLabel": {"formatter": "{value} °C"},
                },
            ],
            "series": [
                {
                    "name": "蒸发量",
                    "type": "bar",
                    "data": [
                        2.0,
                        4.9,
                        7.0,
                        23.2,
                        25.6,
                        76.7,
                        135.6,
                        162.2,
                        32.6,
                        20.0,
                        6.4,
                        3.3,
                    ],
                },
                {
                    "name": "降水量",
                    "type": "bar",
                    "data": [
                        2.6,
                        5.9,
                        9.0,
                        26.4,
                        28.7,
                        70.7,
                        175.6,
                        182.2,
                        48.7,
                        18.8,
                        6.0,
                        2.3,
                    ],
                },
                {
                    "name": "平均温度",
                    "type": "line",
                    "yAxisIndex": 1,
                    "data": [
                        2.0,
                        2.2,
                        3.3,
                        4.5,
                        6.3,
                        10.2,
                        20.3,
                        23.4,
                        23.0,
                        16.5,
                        12.0,
                        6.2,
                    ],
                },
            ],
        }
        st_echarts(options)


def render_custom_pie():
    with st.echo("below"):
        pie_options = {
            "backgroundColor": "#2c343c",
            "title": {
                "text": "Customized Pie",
                "left": "center",
                "top": 20,
                "textStyle": {"color": "#ccc"},
            },
            "tooltip": {"trigger": "item", "formatter": "{a} <br/>{b} : {c} ({d}%)"},
            "visualMap": {
                "show": False,
                "min": 80,
                "max": 600,
                "inRange": {"colorLightness": [0, 1]},
            },
            "series": [
                {
                    "name": "Source of interview",
                    "type": "pie",
                    "radius": "55%",
                    "center": ["50%", "50%"],
                    "data": [
                        {"value": 235, "name": "Video Ad"},
                        {"value": 274, "name": "Affiliate Ad"},
                        {"value": 310, "name": "Email marketing"},
                        {"value": 335, "name": "Direct access"},
                        {"value": 400, "name": "Search engine"},
                    ],
                    "roseType": "radius",
                    "label": {"color": "rgba(255, 255, 255, 0.3)"},
                    "labelLine": {
                        "lineStyle": {"color": "rgba(255, 255, 255, 0.3)"},
                        "smooth": 0.2,
                        "length": 10,
                        "length2": 20,
                    },
                    "itemStyle": {
                        "color": "#c23531",
                        "shadowBlur": 200,
                        "shadowColor": "rgba(0, 0, 0, 0.5)",
                    },
                    "animationType": "scale",
                    "animationEasing": "elasticOut",
                }
            ],
        }
        st_echarts(options=pie_options)


def render_effect_scatter():
    with st.echo("below"):
        options = {
            "xAxis": {"scale": True},
            "yAxis": {"scale": True},
            "series": [
                {
                    "type": "effectScatter",
                    "symbolSize": 20,
                    "data": [[161.2, 51.6], [167.5, 59]],
                },
                {
                    "type": "scatter",
                    "data": [
                        [161.2, 51.6],
                        [167.5, 59.0],
                        [159.5, 49.2],
                        [157.0, 63.0],
                        [155.8, 53.6],
                        [170.0, 59.0],
                        [159.1, 47.6],
                        [166.0, 69.8],
                        [176.2, 66.8],
                        [160.2, 75.2],
                        [172.5, 55.2],
                        [170.9, 54.2],
                        [172.9, 62.5],
                        [153.4, 42.0],
                        [160.0, 50.0],
                        [176.5, 71.8],
                        [164.4, 55.5],
                        [160.7, 48.6],
                        [174.0, 66.4],
                        [163.8, 67.3],
                    ],
                },
            ],
        }
        st_echarts(options)


def render_calendar_heatmap():
    with st.echo("below"):

        def get_virtual_data(year):
            date_list = pd.date_range(
                start=f"{year}-01-01", end=f"{year + 1}-01-01", freq="D"
            )
            return [[d.strftime("%Y-%m-%d"), randint(1, 10000)] for d in date_list]

        options = {
            "title": {"top": 30, "left": "center", "text": "2016年某人每天的步数"},
            "tooltip": {},
            "visualMap": {
                "min": 0,
                "max": 10000,
                "type": "piecewise",
                "orient": "horizontal",
                "left": "center",
                "top": 65,
                "textStyle": {"color": "#000"},
            },
            "calendar": {
                "top": 120,
                "left": 30,
                "right": 30,
                "cellSize": ["auto", 13],
                "range": "2016",
                "itemStyle": {"borderWidth": 0.5},
                "yearLabel": {"show": False},
            },
            "series": {
                "type": "heatmap",
                "coordinateSystem": "calendar",
                "data": get_virtual_data(2016),
            },
        }
        st_echarts(options)


def render_treemap():
    with st.echo("below"):
        options = {
            "series": [
                {
                    "type": "treemap",
                    "data": [
                        {
                            "name": "nodeA",
                            "value": 10,
                            "children": [
                                {"name": "nodeAa", "value": 4},
                                {"name": "nodeAb", "value": 6},
                            ],
                        },
                        {
                            "name": "nodeB",
                            "value": 20,
                            "children": [
                                {
                                    "name": "nodeBa",
                                    "value": 20,
                                    "children": [{"name": "nodeBa1", "value": 20}],
                                }
                            ],
                        },
                    ],
                }
            ]
        }
        st_echarts(options)


def render_datazoom():
    with st.echo("below"):
        data = [
            ["14.616", "7.241", "0.896"],
            ["3.958", "5.701", "0.955"],
            ["2.768", "8.971", "0.669"],
            ["9.051", "9.710", "0.171"],
            ["14.046", "4.182", "0.536"],
            ["12.295", "1.429", "0.962"],
            ["4.417", "8.167", "0.113"],
            ["0.492", "4.771", "0.785"],
            ["7.632", "2.605", "0.645"],
            ["14.242", "5.042", "0.368"],
        ]
        option_js = {
            "xAxis": {"type": "value"},
            "yAxis": {"type": "value"},
            "dataZoom": [{"type": "slider", "start": 10, "end": 60}],
            "series": [
                {
                    "type": "scatter",
                    "itemStyle": {"opacity": 0.8},
                    "symbolSize": JsCode(
                        """function (val) {  return val[2] * 40; }"""
                    ).js_code,
                    "data": data,
                }
            ],
        }
        st_echarts(options=option_js)


def render_dataset():
    with st.echo("below"):
        options = {
            "legend": {},
            "tooltip": {},
            "dataset": {
                "source": [
                    ["product", "2015", "2016", "2017"],
                    ["Matcha Latte", 43.3, 85.8, 93.7],
                    ["Milk Tea", 83.1, 73.4, 55.1],
                    ["Cheese Cocoa", 86.4, 65.2, 82.5],
                    ["Walnut Brownie", 72.4, 53.9, 39.1],
                ]
            },
            "xAxis": {"type": "category"},
            "yAxis": {},
            "series": [{"type": "bar"}, {"type": "bar"}, {"type": "bar"}],
        }
        st_echarts(options, renderer="svg")


def render_map():
    with st.echo("below"):
        options = {
            "backgroundColor": "#404a59",
            "title": {
                "text": "全国主要城市空气质量",
                "subtext": "data from PM25.in",
                "sublink": "http://www.pm25.in",
                "left": "center",
                "textStyle": {"color": "#fff"},
            },
            "tooltip": {"trigger": "item"},
            "legend": {
                "orient": "vertical",
                "top": "bottom",
                "left": "right",
                "data": ["pm2.5"],
                "textStyle": {"color": "#fff"},
            },
            "visualMap": {
                "min": 0,
                "max": 300,
                "splitNumber": 5,
                "color": ["#d94e5d", "#eac736", "#50a3ba"],
                "textStyle": {"color": "#fff"},
            },
            "geo": {
                "map": "china",
                "label": {"emphasis": {"show": False}},
                "itemStyle": {
                    "normal": {"areaColor": "#323c48", "borderColor": "#111"},
                    "emphasis": {"areaColor": "#2a333d"},
                },
            },
        }
        st_echarts(options)


def render_event():
    with st.echo("below"):
        st.markdown("Click on chart elements")
        options = {
            "xAxis": {
                "data": ["shirt", "cardign", "chiffon shirt", "pants", "heels", "socks"]
            },
            "yAxis": {},
            "series": [
                {"name": "sales", "type": "bar", "data": [5, 20, 36, 10, 10, 20]}
            ],
        }
        events = {"click": "function(params, echarts) {alert('click detection');}"}
        st_echarts(options, events=events)


if __name__ == "__main__":
    main()
app_pyecharts.py
import random

import streamlit as st
from pyecharts import options as opts
from pyecharts.charts import Bar
from pyecharts.charts import Geo
from pyecharts.charts import Timeline
from pyecharts.commons.utils import JsCode
from pyecharts.faker import Faker

from streamlit_echarts import st_pyecharts


def main():
    PAGES = {
        "Basic rendering": render_basic,
        "Custom themes": render_custom,
        "Filter with legend": render_filter_legend,
        "Vertical datazoom": render_vertical_datazoom,
        "Timeline": render_timeline,
        "Chart with randomization": render_randomize,
        "JsCode coloring": render_js,
        "Map": render_map,
    }

    st.header("Hello Pyecharts !")
    st.sidebar.header("Configuration")
    page = st.sidebar.selectbox("Choose an example", options=list(PAGES.keys()))
    PAGES[page]()


def render_basic():
    with st.echo("below"):
        b = (
            Bar()
            .add_xaxis(["Microsoft", "Amazon", "IBM", "Oracle", "Google", "Alibaba"])
            .add_yaxis(
                "2017-2018 Revenue in (billion $)", [21.2, 20.4, 10.3, 6.08, 4, 2.2]
            )
            .set_global_opts(
                title_opts=opts.TitleOpts(
                    title="Top cloud providers 2018", subtitle="2017-2018 Revenue"
                ),
                toolbox_opts=opts.ToolboxOpts(),
            )
        )
        st_pyecharts(b)


def render_custom():
    with st.echo("below"):
        b = (
            Bar()
            .add_xaxis(["Microsoft", "Amazon", "IBM", "Oracle", "Google", "Alibaba"])
            .add_yaxis(
                "2017-2018 Revenue in (billion $)", [21.2, 20.4, 10.3, 6.08, 4, 2.2]
            )
            .set_global_opts(
                title_opts=opts.TitleOpts(
                    title="Top cloud providers 2018", subtitle="2017-2018 Revenue"
                )
            )
        )
        st_pyecharts(b, theme="dark")

        st_pyecharts(
            b,
            theme={
                "backgroundColor": "#f4cccc",
                "textStyle": {"color": "rgba(255, 0, 0, 0.8)"},
            },
        )


def render_filter_legend():
    with st.echo("below"):
        c = (
            Bar(
                init_opts=opts.InitOpts(
                    animation_opts=opts.AnimationOpts(
                        animation_delay=1000, animation_easing="elasticOut"
                    )
                )
            )
            .add_xaxis(Faker.choose())
            .add_yaxis("商家A", Faker.values())
            .add_yaxis("商家B", Faker.values())
            .set_global_opts(
                title_opts=opts.TitleOpts(title="Bar-动画配置基本示例", subtitle="我是副标题")
            )
        )
        st_pyecharts(c)


def render_vertical_datazoom():
    with st.echo("below"):
        c = (
            Bar()
            .add_xaxis(Faker.days_attrs)
            .add_yaxis("商家A", Faker.days_values, color=Faker.rand_color())
            .set_global_opts(
                title_opts=opts.TitleOpts(title="Bar-DataZoom(slider-垂直)"),
                datazoom_opts=opts.DataZoomOpts(orient="vertical"),
            )
        )
        st_pyecharts(c, height="400px")


def render_timeline():
    with st.echo("below"):
        x = Faker.choose()
        tl = Timeline()
        for i in range(2015, 2020):
            bar = (
                Bar()
                .add_xaxis(x)
                .add_yaxis("商家A", Faker.values())
                .add_yaxis("商家B", Faker.values())
                .set_global_opts(title_opts=opts.TitleOpts("某商店{}年营业额".format(i)))
            )
            tl.add(bar, "{}年".format(i))
        st_pyecharts(tl)


def render_randomize():
    with st.echo("below"):
        b = (
            Bar()
            .add_xaxis(["Microsoft", "Amazon", "IBM", "Oracle", "Google", "Alibaba"])
            .add_yaxis(
                "2017-2018 Revenue in (billion $)", random.sample(range(100), 10)
            )
            .set_global_opts(
                title_opts=opts.TitleOpts(
                    title="Top cloud providers 2018", subtitle="2017-2018 Revenue"
                ),
                toolbox_opts=opts.ToolboxOpts(),
            )
        )
        st_pyecharts(
            b, key="echarts"
        )  # Add key argument to not remount component at every Streamlit run
        st.button("Randomize data")


def render_js():
    with st.echo("below"):
        st.markdown(
            """Overwrite chart colors with JS. 
        Under 50 : red. Between 50 - 100 : blue. Over 100 : green"""
        )
        color_function = """
                function (params) {
                    if (params.value > 0 && params.value < 50) {
                        return 'red';
                    } else if (params.value > 50 && params.value < 100) {
                        return 'blue';
                    }
                    return 'green';
                }
                """
        c = (
            Bar()
            .add_xaxis(Faker.choose())
            .add_yaxis(
                "商家A",
                Faker.values(),
                itemstyle_opts=opts.ItemStyleOpts(color=JsCode(color_function)),
            )
            .add_yaxis(
                "商家B",
                Faker.values(),
                itemstyle_opts=opts.ItemStyleOpts(color=JsCode(color_function)),
            )
            .add_yaxis(
                "商家C",
                Faker.values(),
                itemstyle_opts=opts.ItemStyleOpts(color=JsCode(color_function)),
            )
            .set_global_opts(title_opts=opts.TitleOpts(title="Bar-自定义柱状颜色"))
        )
        st_pyecharts(c)


def render_map():
    with st.echo("below"):
        g = (
            Geo()
            .add_schema(maptype="china")
            .add("geo", [list(z) for z in zip(Faker.provinces, Faker.values())])
            .set_series_opts(label_opts=opts.LabelOpts(is_show=False))
            .set_global_opts(
                visualmap_opts=opts.VisualMapOpts(),
                title_opts=opts.TitleOpts(title="Geo-基本示例"),
            )
        )
        st_pyecharts(g)


if __name__ == "__main__":
    main()

You should be able to at least copy examples from echarts examples and add quotes everywhere to make it a Python dict before passing to st_echarts, or Pyecharts examples by removing .render() part before sending to Streamlit component st_pyecharts. I’ll be focusing more on echarts examples than pyecharts for now.

Still working on documenting everything, so most of the documentation is the provided 2 sample files :laughing:

Other caveats to be aware for this build :

  • only China maps work
  • only global mouse events work
  • Defining the theme in Pyecharts when instantiating chart like Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT))
    does not work, you need to call theme in st_pyecharts(c, theme=ThemeType.LIGHT).

st_pyecharts vs st.html

While this package provides a st_pyecharts method, if you’re using pyecharts you can directly embed your pyecharts visualization inside st.html by passing the output of the chart’s .render_embed().

from pyecharts.charts import Bar
from pyecharts import options as opts
import streamlit as st

c = (Bar()
    .add_xaxis(["Microsoft", "Amazon", "IBM", "Oracle", "Google", "Alibaba"])
    .add_yaxis('2017-2018 Revenue in (billion $)', [21.2, 20.4, 10.3, 6.08, 4, 2.2])
    .set_global_opts(title_opts=opts.TitleOpts(title="Top cloud providers 2018", subtitle="2017-2018 Revenue"),
                     toolbox_opts=opts.ToolboxOpts())
    .render_embed() # generate a local HTML file
)
st.html(c, width=1000, height=1000)

Using st_pyecharts is still something you would want if you need to change data regularly
without remounting the component, check for examples app_pyecharts.py for Chart with randomization example.

9 Likes

Woohoo! Thanks for putting this together, and writing some a thorough post about it, @andfanilo!

(And also, for going the extra mile and publishing the component as a wheel!)

This is awesome.

6 Likes

@andfanilo Haven’t heard of this library. What is the advantage of this over Vega Lite which I personally love but also has a streamlit implemenation

Hey @func4plus1,

I’m also a regular user of Altair/Vega-lite for Data science/EDA. For echarts I do find the level of interactivity and native transitions between states in the chart super nice to look at with not too much code :slight_smile: plus they support tons of chart types. For slick demo data apps to show results to customers I may go for echarts. Check out their examples page.

In the hand if you’re a Vega-lite user, juggling between it and echarts shouldn’t be too hard.

2 Likes

Thanks for your awesome component !
Is it possible to apply python lambda to echarts option value with some function type (like formatter):

Hello @starchris, welcome to the community!

Have a look at the Github README on applying a JS lambda (and an example code). No Python lambda function though.

If the legend property is in the echarts options it should work, though I admit I did not extensively test it, so feel free to file an issue if for example multiline JsCode doesn’t work for example.

Also does the string template work correctly ?

Best,
Fanilo.

Been a long time!
I’ve just released v0.2.0, which includes the liquidfill and wordcloud extensions.

image

Try on https://share.streamlit.io/andfanilo/streamlit-echarts-demo/app.py
Upgrade: pip install -U streamlit-echarts

Hopefully next version I get to upgrade to echarts 5 !

Cheers,
Fanilo

3 Likes

Great work, Fanilo!

But when I tried to use this component, an error comes with “Your app is having trouble loading Streamlit_echarts.st_echarts component (This app is attempting to load the component from **, and hasn’t received its “Streamlit:componentReady” ** message)” .

Is there any way to solve this?

1 Like

@imClumsyPanda Hmmm you’re right this is odd, lemme check this :slight_smile: thanks!

EDIT: so uh I had the same bug as you did 2 hours ago on Streamlit Share but it doesn’t happen anymore :confused: https://share.streamlit.io/andfanilo/streamlit-echarts-demo/app.py how about you?

I reinstalled the package and now it’s solved! Thanks a lot and this component really helps a lot!

1 Like

(upload://vFu8f59RYQq6m9DrqeKarZPNreH.jpeg)

How can I deal with this problem?

Hi @andfanilo! Thanks for the great component! However, I am facing the same issue as @imClumsyPanda. When running in local component loads correctly, however, when deployed in an AWS EC2 instance, the following error appears:

**“Your app is having trouble loading Streamlit_echarts.st_echarts component (This app is attempting to load the component from , and hasn’t received its “Streamlit:componentReady” ** message)”

I have tried reinstalling and uploading but it does not solve the problem. Sometimes, after the page is loaded several times, it works, but when trying in incognito window, it does not. :frowning:

Thank you!

Hey @Marina_Alonso, thanks for reaching out!

Yeah I’m not sure yet why it does that sometimes, it looks like the following issue where if the component is not loaded fast enough then Streamlit timeouts it. Maybe I could try making the final package less big but I don’t know how yet.

Let me look into it. I’m currently working on upgrading to echarts 5 so I may squeeze some size optimization.

Have a good day,
Fanilo

1 Like

Streamlit-Echarts v0.3.0 released!

:lipstick: Upgraded to echarts v5, check out those new default colors (and probably other features to customize your chart even further)!

:world_map: Added a map parameter to register a custom geoJSON to display

image

image

App: https://share.streamlit.io/andfanilo/streamlit-echarts-demo/app.py
Install: pip install -U streamlit-echarts

3 Likes

Looks very powerful. How do you rate this lib vs. Altair or Plotly?

This is how I usually use them:

  • Plotly: quick interactive plots with Plotly express, my goto when I’m building a Streamlit prototype with interactive graphs in a very few days so don’t care about customizing, I just go with the plotly-white theme.
  • Altair: I like the tighter plots and overall feel of Altair, how the API is designed which is not too much “ggplotty”, how it manages faceting and the way it maps to the vega-lite format. You do need to go back and forth between the Altair API and Vega-lite docs to get the best of it. So I use it for static plots that go on Powerpoint slides (I’m really not good enough with Matplotlib/Seaborn XD), also because I find it hard to customize the interactivity and animation sometimes.
  • ECharts: so many options (from networks to 3D maps), so customizable, so slick, and the interactivity when data changes is stunning, it has this D3.js feeling! But the documentation can be hard to navigate and building the big JSON takes time. I’ve never really managed to get into pyecharts too :confused: . I go with echarts when I want really eye-popping D3 like interactive graphs on Streamlit apps.
1 Like

Thanks @andfanilo for the comparison and use cases. Not sure my audience appreciates eye-popping, but they do want cross-filtering. Is that what you mean by “interactivity when data changes” in echarts?

Ah yeah in my head when I say “interactivity” I think “clicking a legend to transition from one to another displayed data” like here, just love how smooth the transition is.

For cross filtering I usually go with Altair. I have no idea how to do this in eCharts yet, I guess it’s possible by abusing Events & Actions. In Plotly you would need Dash or custom Streamlit events. In both echarts and Plotly you can have subplots sharing one zoom axis but no “true cross filtering”.

1 Like

Hi @andfanilo

Excellent work on this package!

Just chipping in to say I am also experiencing the problems with

**“Your app is having trouble loading Streamlit_echarts.st_echarts component (This app is attempting to load the component from , and hasn’t received its “Streamlit:componentReady” ** message)”

with version 0.3.0 of streamlit-echarts, but not consistently. It might be the case that the issue you’re linking could fix it. Anyway, just wanted to share the issue, not expecting any immediate resolution…

Thanks for reporting @PeterT. It’s hard to debug this because it happens inconsistently and not on every user device, but I saw this issue appear on a lot of components so I’ll have to take a deeper look too someday…

Fanilo