Back to Blog
·5 min read·Blog

How to build interactive charts in python using htmx and echarts

This article will show you how to build interactive charts in python using htmx and echarts. You will learn how to use include other libraries in your htmx workflow using htmx extensions.

Themba Mahlangu

Themba Mahlangu

How to build interactive charts in python using htmx and echarts

In this post, we will show you how to include javascript libraries like echarts into your htmx workflow using htmx extensions.

Prerequisites

Basic knowledge of pyecharts & htmx - If you haven't already, please check out this post about how to set up pyecharts for your Django/Python application.

The code for this tutorial can be found here

Concepts Covered

We want to make our filters to the chart so we can filter on dates.

  • htmx extensions

    • we will create an htmx extension to handle parsing data from the backend and updating the chart.
  • triggering client side

    • actions from the backend
  • hyperscript

    • a small example of how to use the hyperscript library with htmx

Chart View

We will create a view to handle htmx requests. The view will use parameters provided in the request to return data for the correct period and chart type.

def chart_view_hx(request):
  """Returns chart options for echarts"""
  
  period = request.GET.get("period", "week")
  chart_id = request.GET.get("chart_id")
  chart_type = request.GET.get("chart_type")
  
  days_in_period = {
    "week": 7,
    "month": 30,
  }
  filter_by = days_in_period.get(period,"week")

  # simulate fetching this from your database
  chart_title = CHART_TYPES.get(chart_type, "page_views")
  chart_data = fake_chart_data(filter_by, chart_title)

   # render the chart and update options to include id
  chart = line_chart(chart_data)
  chart.options["id"] = chart_id
  chart._prepare_render()
  data = chart.json_contents
  
  response = HttpResponse(content=chart.json_contents)
  
  # optional 
  # if using the django_htmx library you can attach any clientside 
  # events here . For example

  # trigger_client_event(response, 'filterchanged', params={})
  return response

Notes

period = request.GET(..)

  • fetch the period, chart_id, chart_type from the request

chart_data = fake_chart_data()

  • this simulates fetching data from an api or your database.

chart = line_chart(chart_data)

  • call the method that build the chart. This method will return an instance of a Line chart with all the options we need to update the chart

chart.options["id"]

  • here we add the id of the chart we want to update and then call the prepare_render method to ensure that the data is converted to json.

trigger_client_event

  • If you need to piggyback additional events to be triggered on the client-side, you can attach them here.

    If you are using Django, the django_htmx library provides the trigger_client_event method for this

    You can also do this for yourself by attaching the HX-Trigger to the response. See the htmx docs for more details on this.

Update the urls

  • django_project/urls.py
urlpatterns = [
 ...
    path('charts', chart_view_hx, name='charts-hx')
 ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

Notes

path('charts', ..)

  • add the view to urls.py.

static(settings.STATIC_URL..)

  • You will notice we have also added the Django static helper function here so we can staticfiles in debug mode.

Add htmx extension

Htmx provides an extension mechanism for defining and using extensions within htmx-based applications.

htmx.defineExtension('echarts', {
    transformResponse: function (text, xhr, elt) {
        // parse json data
        var data = JSON.parse(text);

        // fetch echart element
        var option = data;
        var chartContainer = document.getElementById(data.id);
        var chart = echarts.getInstanceByDom(chartContainer);

        // clean up options and update chart
        delete data.id;
        chart.setOption(option);

    }
});

Notes

htmx.defineExtension('echarts', {}

  • define an extension called echarts. This will be referenced on the element by setting the hx-ext attribute i.e.
    <div hx-ext='echarts'>

var data
= JSON(parse(text)) - parse the chart data from the backend

var chartContainer
= document.getElementById(data.id); - fetch the chartContainer element from the DOM using the id provided in our data

delete data.id

  • we clean up our options object to remove the id. We only needed the Id so we could identify the chart to update.

chart.setOption(option)

  • finally, use the setOption() method from the echarts library to update the data on the chart.

Update the templates

Next, we need to update our chart template to include the filter buttons.

We will create a new filter_button component for the filters.

  <button  
    hx-get="{% url 'charts-hx' %}?chart_id={{ c.chart_id }}&amp;chart_type=page_views&amp;period={{period}}"
    hx-swap="none"
    id="id_last_week_filter_btn"  
    class="bg-white rounded shadow p-2 text-sm flex items-center font-semibold mr-2 cursor-pointer
    {% if selected %}text-indigo-600 {% else %}text-gray-600 {% endif %}">{{label}}
  </button>

Notes

hx-get="{% url 'charts-hx' %}
... - include the url and required parameters

hx-swap ='none'

  • set this to none, the response from the backend will not be swapped out.

{{chart_type}},{{chart_id}},{{period}}

  • the component expects these variables to be available in the context

{% if selected .. %}

  • here we are adding a bit of styling to highlight the selected button.

The main template will remain largely unchanged from the previous tutorial. We have modified it to include the filter_buttons. To keep things simple, we have removed the additional charts.

<head>
  ...
  <script src="https://unpkg.com/htmx.org@1.7.0"></script>
  <script src="{% static 'js/ext/hx-echarts.js'%}"></script>
  ...
</head>
<div class='flex' hx-ext="echarts">
   {% include 'components/chart_filter.html' with period='week' c=charts.0 label='Last Week' selected=True %}
   {% include 'components/chart_filter.html' with period='month' c=charts.0 label='Last Month' %} 
</div> 

Notes

<head >..</head>

  • the head section has been modified to include the htmx library, the echarts extension script.

{% include 'components/chart_filter.html'

  • include the components in the main template and define the variables for the chart, label, the period to be used in the filter as well as the

UI Improvements

We can improve it by letting our users know which tab is active. This can be achieved using vanilla javascript or alpinejs. In this example, we will use hyperscript, a companion library for htmx to achieve this.

<head>
  ...
  <script src="https://unpkg.com/htmx.org@1.7.0"></script>
  <script src="https://unpkg.com/hyperscript.org@0.9.5"></script>
  <script src="{% static 'js/ext/hx-echarts.js'%}"></script>
  ...
</head>
<div class='flex' hx-ext="echarts" 
    _="on htmx:afterOnLoad take .text-indigo-600 for event.target"
 >
   {% include 'components/chart_filter.html' with period='week' c=charts.0 label='Last Week' selected=True %}
   {% include 'components/chart_filter.html' with period='month' c=charts.0 label='Last Month' %} 
</div>

Notes

<head></head>

  • we have updated the head section to include hyperscript.

_="on htmx:afterOnLoad take
..." after the response is received, the text-indigo-600 class will be moved to the clicked button.

Conclusion

In this post, we learned how to use htmx extensions with the echarts library. The htmx library provides a convenient mechanism to organize your codebase and integrate other libraries into your htmx workflow.

Further reading

htmx docs

hyperscript docs

echarts docs