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.