Phoenix LiveView chart.js setup

By Boobalan AP

Boobalan AP
Image for post
Image for post
Final output

Project creation

In terminal use mix to create a new live view project without ecto.

mix phx.new chart --live --no-ecto

and press ‘y’ when asked to install dependency.

We are going to work on these following files.

  • lib > chart_web > live > page_live.ex
  • lib > chart_web > live > page_live.html.leex
  • lib > chart_web > templates > layout > root.html.leex
  • assets > js > app.js

Cleanup

In page_live.ex delete all other functions except mount. Modify mount function such that it returns only

{:ok, socket}

In page_live.html.leex delete entire stuff.

In root.html.ex replace the entire <header> block with

<h1>Phoenix LiveView Chart.js</h1>

Now lets start the server with

mix phx.server

Initial try

Let’s start by following the documentation at https://www.chartjs.org/docs/latest/getting-started/

Let’s add the following in page_live.html.leex. Here we are just rendering a canvas and including the chart.js library script.

<canvas id="myChart"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>

Next the document suggest us to add the following in a js file. We have app.js where we can add the following. We are not changing anything. We just want follow the documentation and observe the result.

var ctx = document.getElementById('myChart').getContext('2d');var chart = new Chart(ctx, { // The type of chart we want to create type: 'line', // The data for our dataset data: { labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], datasets: [{ label: 'My First dataset', backgroundColor: 'rgb(255, 99, 132)', borderColor: 'rgb(255, 99, 132)', data: [0, 10, 5, 2, 20, 30, 45] }] }, // Configuration options go here options: {}

});

When we restart the server using mix phx.server, Voilà!! we get a glimpse of chart.

:( But it shrinks.

Towards Hooks

Here we are going to work with both chart.js library and liveview. So let’s check the documentation at https://hexdocs.pm/phoenix_live_view/js-interop.html

Let’s add phx-hook=”chart” to canvas.

If we run again we are greeted with an error as follows in the browser console.

unknown hook found for "chart" <canvas id="myChart" phx-hook="chart">

Gaining some insights from the error and the documentation, we add the following hook to the app.js. Note that we moved the chart creation from outside to mounted function of chart hook.

let hooks = {}hooks.chart = { mounted() { var ctx = this.el.getContext('2d'); var chart = new Chart(ctx, { // The type of chart we want to create type: 'line', // The data for our dataset data: { labels: ['January', 'February', 'March','April', 'May', 'June', 'July'], datasets: [{ label: 'My First dataset', backgroundColor: 'rgb(255, 99, 132)', borderColor: 'rgb(255, 99, 132)', data: [0, 10, 5, 2, 20, 30, 45] }] }, // Configuration options go here options: {} }); }

}

let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}, hooks})

We also changed ctx from

document.getElementById('myChart').getContext('2d');

to

this.el.getContext('2d');

as we can get the element through this.el.

We then pass the hooks along with params in LiveSocket creation.

Now we are able to see a chart, without any collapse.

Updating the chart using server push

Let’s check how to update the chart at https://www.chartjs.org/docs/latest/developers/updates.html

As per the documentation we have to change the data and just issue a chart.update() command.

The updates we push from server are received using this.handleEvent(event_name, callback) inside mount.

The updates are sent using socket |> push_event(event_name, payload) from the server.

this.handleEvent("points", ({points}) => { chart.data.datasets[0].data = points chart.update()

})

defmodule ChartWeb.PageLive do
use ChartWeb, :live_view
@impl true def mount(_params, _session, socket) do schedule_update() {:ok, socket}

end

@impl true def handle_info(:update, socket) do schedule_update() {:noreply, socket |> push_event("points", %{points: get_points})}

end

defp schedule_update, do: self() |> Process.send_after(:update, 2000) defp get_points, do: 1..7 |> Enum.map(fn _ -> :rand.uniform(100) end)

end

In order to simulate server push, we just schedule and update message. On arrival of that we are pushing random values to the client.

Another interaction

If we add another interaction like incrementing a count on press of a button, it breaks.

page_live.ex and page_live.html.leex

@impl true def mount(_params, _session, socket) do schedule_update() {:ok, socket |> assign(count: 0)}

end

@impl true def handle_event("increment", _, socket) do {:noreply, socket |> update(:count, &(&1 + 1))}

end

<h2><%= @count %></h2>
<button phx-click="increment">INCR</button>

This happens if we update the UI after the hook has been mounted.

In order to solve this we have to ignore the UI updates on canvas. So we enclose canvas with a container div which has phx-update=”ignore”

<div phx-update="ignore"> <canvas id="myChart" phx-hook="chart"></canvas>

</div>

Combine button and chart

Let’s combine button interaction and chart updation as follows.

page_live.ex and page_live.html.leex

@impl true def mount(_params, _session, socket) do {:ok, socket}

end

@impl true def handle_event("next", _, socket) do {:noreply, socket |> push_event("points", %{points: get_points()})}

end

<button phx-click="next">NEXT RANDOM CHART</button>

Now on each button press a chart with random values is generated.

Codebase @

https://github.com/apboobalan/chart.git