Animated Echarts with Panel

Dear Community,

I am currently in the process of creating dashboards using Echarts within Panel. I truly like Echarts for its styling, animations and extensive configurability. The Apache Echarts gallery, which can be found at Examples - Apache ECharts, provides a wide range of excellent examples for various chart types. Thanks to Panel’s Echarts rendering Apache Echarts, most of these examples can be seamlessly integrated into Panel’s pane.

However, Panel currently doesn’t support javascript function with the ECharts pane particularly for more advanced charts. As a workaround, some suggestions were made to modify formatter parameters of the chart Display negative numbers as positive numbers in bar type ECharts.

I am now wondering whether it’s feasible to integrate animated ECharts, such as the Line Race (Examples - Apache ECharts) or Bar Race (Examples - Apache ECharts) charts with animationDuration, setTimeout and setInterval parameters into Panel template with Panel Echarts or pyecharts.
Thank you in advance for any hints!

Br, Sergey

1 Like

I managed to get Bar Race with pn.pane.HTML()

import panel as pn

pn.extension()
# js_files = {"echarts5": "https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"}
# pn.extension(js_files=js_files)

bar_color = "green"
html = f"""
    <body style="height: 100%; margin: 0">
    
        <div id="main" style="height: 100%"></div>
        <script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <!-- Include jQuery -->
        <script type="text/javascript">

var option;

const data = [];
for (let i = 0; i < 5; ++i) {{
  data.push(Math.round(Math.random() * 200));
}}
option = {{
  xAxis: {{
    max: 'dataMax'
  }},
  yAxis: {{
    type: 'category',
    data: ['A', 'B', 'C', 'D', 'E'],
    inverse: true,
    animationDuration: 300,
    animationDurationUpdate: 300,
    max: 2 // only the largest 3 bars will be displayed
  }},
  series: [
    {{
      realtimeSort: true,
      name: 'X',
      type: 'bar',
      data: data,
      label: {{
        show: true,
        position: 'right',
        valueAnimation: true
      }},
      itemStyle: {{
        color: '{bar_color}' 
      }}
    }}
  ],
  legend: {{
    show: true
  }},
  animationDuration: 0,
  animationDurationUpdate: 3000,
  animationEasing: 'linear',
  animationEasingUpdate: 'linear'
}};
function run() {{
  for (var i = 0; i < data.length; ++i) {{
    if (Math.random() > 0.9) {{
      data[i] += Math.round(Math.random() * 2000);
    }} else {{
      data[i] += Math.round(Math.random() * 200);
    }}
  }}
  myChart.setOption({{
    series: [
      {{
        type: 'bar',
        data
      }}
    ]
  }});
}}

setInterval(function () {{
  run();
}}, 3000);

var chartDom = document.getElementById("main");
var myChart;

function initializeChart() {{
    myChart = echarts.init(chartDom);
    if (option && typeof option === 'object') {{
        myChart.setOption(option);
    }}

    setTimeout(function () {{
    run();
    }}, 0);
}}

var script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js';  // Specify the ECharts version
script.onload = initializeChart;
document.head.appendChild(script);

        </script>
    </body>
"""

bar_race = pn.pane.HTML(html, width=500, height=500)
tmpl = pn.template.MaterialTemplate(title="Bar Race")
tmpl.main.append(bar_race)
tmpl.servable()

However, when I attempt to incorporate a widget for altering the color of the bar, the widget doesn’t appear visible. It appears that the chart is covering up all other content. This issue might be related to the “id” attribute being set to “main” in the JavaScript script. However, altering the ID name results in an error: “Uncaught TypeError: Cannot read properties of null (reading ‘getAttribute’).”

import panel as pn

pn.extension()
# js_files = {"echarts5": "https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"}
# pn.extension(js_files=js_files)

bar_color = pn.widgets.RadioButtonGroup(
    name="Select Color",
    options=["green", "red", "blue"],
    button_type="success",
    button_style="outline",
    orientation="horizontal",
)


@pn.depends(bar_color.param.value)
def change_bar_color(color):
    html = f"""
        <body style="height: 100%; margin: 0">
        
            <div id="main" style="height: 100%"></div>
            <script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <!-- Include jQuery -->
            <script type="text/javascript">

    var option;

    const data = [];
    for (let i = 0; i < 5; ++i) {{
    data.push(Math.round(Math.random() * 200));
    }}
    option = {{
    xAxis: {{
        max: 'dataMax'
    }},
    yAxis: {{
        type: 'category',
        data: ['A', 'B', 'C', 'D', 'E'],
        inverse: true,
        animationDuration: 300,
        animationDurationUpdate: 300,
        max: 2 // only the largest 3 bars will be displayed
    }},
    series: [
        {{
        realtimeSort: true,
        name: 'X',
        type: 'bar',
        data: data,
        label: {{
            show: true,
            position: 'right',
            valueAnimation: true
        }},
        itemStyle: {{
            color: '{color}' 
        }}
        }}
    ],
    legend: {{
        show: true
    }},
    animationDuration: 0,
    animationDurationUpdate: 3000,
    animationEasing: 'linear',
    animationEasingUpdate: 'linear'
    }};
    function run() {{
    for (var i = 0; i < data.length; ++i) {{
        if (Math.random() > 0.9) {{
        data[i] += Math.round(Math.random() * 2000);
        }} else {{
        data[i] += Math.round(Math.random() * 200);
        }}
    }}
    myChart.setOption({{
        series: [
        {{
            type: 'bar',
            data
        }}
        ]
    }});
    }}

    setInterval(function () {{
    run();
    }}, 3000);

    var chartDom = document.getElementById("main");
    var myChart;

    function initializeChart() {{
        myChart = echarts.init(chartDom);
        if (option && typeof option === 'object') {{
            myChart.setOption(option);
        }}

        setTimeout(function () {{
        run();
        }}, 0);
    }}

    var script = document.createElement('script');
    script.src = 'https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js';  // Specify the ECharts version
    script.onload = initializeChart;
    document.head.appendChild(script);

            </script>
        </body>
    """
    return pn.pane.HTML(html, width=500, height=500)


bar_race = pn.bind(change_bar_color, bar_color)

tmpl = pn.template.MaterialTemplate(title="Bar Race")
tmpl.main.append(pn.Row(bar_color, bar_race))
tmpl.servable()

Hi @svvoronin

Different things are causing the error.

  • The html document already contains an element with id main. I.e. this is the element you select with document.getElementByID('main'). And that is why the widget gets removed.
  • With Panel 1.x your HTML pane is rendering inside shadow dom. So getting handles on the div element you want to render the chart in is not straightforward.

I might be able to work with the script and get it fixed. But instead of using the HTML pane you should be using the ReactiveHTML component. Its made for use cases like these.

I will try to develop an example and share it.

1 Like

Hi @Marc,

Thank you for looking into this matter. Your help is highly appreciated.

Sergey

1 Like

Hi @svvoronin

Here is an example using ReactiveHTML.

import panel as pn
import param
from panel.reactive import ReactiveHTML

pn.extension("echarts")
pn.pane.ECharts

class CustomComponent(ReactiveHTML):
    color=param.Color("pink")
    _template = """<div id="chartDom" style="height: 100%;width:100%"></div>"""
    _scripts = {"render": """
const plot_data = [];
for (let i = 0; i < 5; ++i) {
    plot_data.push(Math.round(Math.random() * 200));
}
const option = {
    xAxis: {
    max: 'dataMax'
    },
    yAxis: {
    type: 'category',
    data: ['A', 'B', 'C', 'D', 'E'],
    inverse: true,
    animationDuration: 300,
    animationDurationUpdate: 300,
    max: 2 // only the largest 3 bars will be displayed
    },
    series: [
    {
    realtimeSort: true,
    name: 'X',
    type: 'bar',
    data: plot_data,
    label: {
        show: true,
        position: 'right',
        valueAnimation: true
    },
    itemStyle: {
        color: data.color
    }
    }
    ],
    legend: {
    show: true
    },
    animationDuration: 0,
    animationDurationUpdate: 3000,
    animationEasing: 'linear',
    animationEasingUpdate: 'linear'
};

const config = {width: model.width, height: model.height}
state.myChart = echarts.init(chartDom,null,config);
state.myChart.setOption(option);
setInterval(function () {self.run();}, 3000);
""",
    "after_layout": "state.myChart.resize();self.run();",
    "run": """
const plot_data = [];
for (let i = 0; i < 5; ++i) {
    plot_data.push(Math.round(Math.random() * 200));
}
const option={series: [{data: plot_data}]}
state.myChart.setOption(option);
""",
    "color": """
option={series: [{itemStyle: {color: data.color}}]}
state.myChart.setOption(option)
"""
    }
component = CustomComponent(height=500, sizing_mode="stretch_width")
pn.Column(
    pn.widgets.RadioButtonGroup.from_param(
        component.param.color,
        name="Select Color",
        value="green",
        options=["green", "red", "blue"],
        button_type="success",
        button_style="outline",
        orientation="horizontal",
    ),
    component
).servable()

You can learn more about ReactiveHTML in

I’m working on much improved ReactiveHTML documentation here

Refactor ReactiveHTML docs by MarcSkovMadsen · Pull Request #5448 · holoviz/panel (github.com)

1 Like

Hi @Marc,

Awesome! Thanks for the fix and the links to the documentation - it’s extremely helpful.

Sergey

1 Like