Beautiful animated charts for LiveView with ECharts

I spotted an article about a View.js component for Apache ECharts yesterday and the demos were lovely, so I immediately wondered if we could have this for LiveView.

Apache ECharts is an Open Source JavaScript Visualization Library. The GitHub repo dates back 9 years so its certainly been around for a long time but somehow evaded me. It can render the charts as either Canvas or SVG, you get to choose when you initialize the chart.

Hooking up to LiveView

We’ll start with a LiveView Hook to set up the chart with the LiveView Hook mounted lifecycle callback.

import * as echarts from '../vendor/echarts.min'

hooks.Chart = {
  mounted() {
    selector = "#" + this.el.id
    this.chart = echarts.init(this.el.querySelector(selector + "-chart"))
    option = JSON.parse(this.el.querySelector(selector + "-data").textContent)
    this.chart.setOption(option)
  }
}

When the element is mounted we initialise the chart and read the JSON data from a hidden element. Now we’ll use this hook to set up a simple example chart.

defmodule DemoWeb.PieLive do
  use DemoWeb, :live_view

  def mount(_params, _session, socket) do
    option = %{
      title: %{text: "π", left: "center", top: "center"},
      series: [
        %{
          type: "pie",
          data: [
            %{name: "A", value: 20},
            %{name: "B", value: 50},
            %{name: "C", value: 100}
          ],
          radius: ["40%", "70%"]
        }
      ]
    }

    {:ok, assign(socket, option: option)}
  end

  def render(assigns) do
    ~H"""
    <div id="pie" phx-hook="Chart">
      <div id="pie-chart" style="width: 400px; height: 400px;" />
      <div id="pie-data" hidden><%= Jason.encode!(@option) %></div>
    </div>
    """
  end
end

And that’s it, we have a nice looking animated pie chart with very little setup required.

Getting live updates

We’re using LiveView so we’re going to want to send live updates to the charts. First we’ll update our Hook to handle the updated lifecycle callback.

import * as echarts from '../vendor/echarts.min'

hooks.Chart = {
  mounted() {
    selector = "#" + this.el.id
    this.chart = echarts.init(this.el.querySelector(selector + "-chart"))
    option = JSON.parse(this.el.querySelector(selector + "-data").textContent)
    this.chart.setOption(option)
  },
+ updated() {
+   selector = "#" + this.el.id
+   option = JSON.parse(this.el.querySelector(selector + "-data").textContent)
+   this.chart.setOption(option)
+ }
}

This fetches the updated data when the component updates and updates the chart. Now we’ll use a gauge chart example to demonstrate the live update.

defmodule DemoWeb.GaugeLive do
  use DemoWeb, :live_view

  def mount(_params, _session, socket) do
    option = %{
      tooltip: %{
        formatter: '{a} <br/>{b} : {c}%'
      },
      series: [
        %{
          name: "Pressure",
          type: "gauge",
          detail: %{formatter: "{value}"},
          data: [
            %{name: "SCORE", value: 50}
          ]
        }
      ]
    }

    {:ok, assign(socket, option: option)}
  end

  def render(assigns) do
    ~H"""
    <div id="gauge" phx-hook="Chart">
      <div id="gauge-chart" style="width: 400px; height: 400px;" phx-update="ignore" />
      <div id="gauge-data" hidden><%= Jason.encode!(@option) %></div>
    </div>
    <.button phx-click="update-chart">Update</.button>
    """
  end

  def handle_event("update-chart", _, socket) do
    option = %{
      series: [
        %{data: [:rand.uniform(100)]}
      ]
    }

    {:noreply, assign(socket, option: option)}
  end
end

We’ve added a button which, when clicked, calls the update-chart event which chooses a new random number and updates the option data, which will trigger the updated Hook callback.

Note we added the phx-update="ignore" attribute to the chart element as well to prevent it disappearing when the component updates.

Here’s a final demo with multiple charts auto-updating every 2 seconds.

There are hundreds of examples available and great documentation to customise your charts and animations exactly how you want them.

If you’ve got any comments or suggestions on how to improve the integration please let me know on Twitter.