LiveView: How to trigger JS function? (Update chart.js on data change)

Hi there,

Disclaimer: I’m a backend developer - you have to deal with that now :slight_smile: LiveView is awesome.

I am trying to integrate a dynamic chart with simple prev/next buttons that change the data.
The liveview part works like a charm.

For the chart I used one of these examples (line chart):

The problem is that if the data array changes, ChartJS doesn’t automatically update.
It has a function .update() that needs to be called (see https://stackoverflow.com/questions/24893872/automatically-update-chartjs-with-knockout-data-bind)

Is there a way to do this through LiveView?
Or how would you achieve this? Do I need to return the complete chart javascript?

Thank you very much!

1 Like

Hi,
I have faced the similar problem (needed to update CSS positioning with LiveView), and described my solution for it here.
Possibly there will be better solution in future.

1 Like

In order to update your charts, you possibly will need to update only the part, that triggers .update() function for your chart.

1 Like

Thank! I will have a look at that.

what I tried meanwhile is to add another (normal) channel. But I’m not very familiar with JS.
If I have this in my assets/js/socket.js:

  channel.on("new_msg", payload => {
    console.log("Received update:", payload);
    myLineChart.update();
  })

It’s getting the messages, but nothing happens. I assume it does not find the myLineChart object defined in the leex template?

What is likely happening is that you defined myLineChart in another module than socket.js.

I’m a bit puzzled by the documentation.

I’d be inclined to uncomment

// import socket from "./socket"

in the app.js module and implement all of

// ...
let channel           = socket.channel("room:lobby", {})
let chatInput         = document.querySelector("#chat-input")
let messagesContainer = document.querySelector("#messages")

chatInput.addEventListener("keypress", event => {
  if(event.keyCode === 13){
    channel.push("new_msg", {body: chatInput.value})
    chatInput.value = ""
  }
})

channel.on("new_msg", payload => {
  let messageItem = document.createElement("li")
  messageItem.innerText = `[${Date()}] ${payload.body}`
  messagesContainer.appendChild(messageItem)
})

channel.join()
  .receive("ok", resp => { console.log("Joined successfully", resp) })
  .receive("error", resp => { console.log("Unable to join", resp) })

in app.js rather than socket.js.

Then in your particular case you should be able to create

let myLineChart = new Chart(...)

inside app.js so that

channel.on("new_msg", payload => {
    console.log("Received update:", payload);
    myLineChart.update();
  })

has access to myLineChart.

In the more general case the module managing myLineChart would export it so that any module that needs access can import it.

Note that the assumption is that you installed Chart.js with npm and imported it into app.js:

import Chart from 'chart.js';

With webpack the a script tag won’t work.

1 Like

Thank you very much I would like to try that.

However, right now I just included the chart in my leex template so I’m able to quick and dirty fill in new data for this test:

  let myLineChart = new Chart(ctx, {
    type: 'line',
    data: {
      labels: [1500,1600,1700,1750,1800,1850,1900,1950,1999,2050],
      datasets: [{ 
        data: [<%= Enum.join(@data, ",") %>],
...

I’m not sure how I would do this in the lightview template then, if I have the chart in app.js?

That would only work with a --no-webpack project. For a bundle the JavaScript has to go into a module - for your experiments app.js. Inside the module you can get ahold of the of the canvas element that is part of your template.

I’m not sure how I would do this in the LiveView template

I’d recommend getting Chart.js with channels to work outside of LiveView first - baby steps.

It should be quite similar to my d3.js demo.

Bummer… I was so excited about Liveview haha :slight_smile:

I’ve changed it to a channel now just for testing.
It’s triggered by liveview clicks.

Sending works great.
However, the chart does not update.

The canvas is defined in my leex template and this is my app.js:
https://gist.github.com/solars/c9ad530cd8b45e45f01bf91981c96985

Did I forget anything?

Get it working with plain EEx first - if LiveView replaces the canvas object with a new one the old Chart would be still rendering to the discarded canvas. But the problem could also be that your payload isn’t the right shape.

You are juggling too many balls in the air …

Bummer… I was so excited about Liveview haha :slight_smile:

I’m not sure what you are expecting.

You chose a client side JS library which renders to a DOM object that is designed to be controlled from the client side. This is a not a use case for LiveView.

LiveView is designed emulate page interactivity by replacing fragments of HTML/CSS/SVG (note the lack of JS). So the LiveView approach to updating a graph would be to generate HTML/CSS/SVG on the server side so that the new fragment can replace the stale one. Unfortunately the aren’t any Elixir server side libraries that match the comprehensive capabilities of client side libraries like Chart.js or d3.js.


Sorry it was my mistake, I tried .eex and got the same result.
It turned out using .push was the problem as I push an array into an array.
If I just set it with = then it works.

I’m was joking, I don’t expect anything from Liveview, I just wanted to express that I think it
is awesome.
I’m a backend developer (as mentioned) so I don’t really have any insights in how all this works (yet).
I thought there might be something to trigger changes in the existing JS through liveview.
As you can see in the thread linked earlier, there seems to be a necessity for it.

I’m unconvinced that particular blend of page manipulation techniques is a genuine use case for LiveView - the tagline is No Need to Write JavaScript.

I have a use case for this, where I need to use client-side encryption (end-to-end encryption via Web Crypto), but with the vast majority of the dynamic functionality being provided by LiveView. LiveView is responsible for rendering encrypted messages on the various connected clients, who then have to perform their own decryption.

I know this is a bit late of a reponse, but have you considered using raw SVGs embedded in your Phoenix templates? I’ve been playing around with it for a while and whilst the learning curve is quite steep, I’ve found it pays off.

Say for example this chart I just added to a website of mine. It needs cleaning up/renaming, but with Liveview it would simply update on the fly :slight_smile:

I got the inspiration from here: How to make charts with SVG. It just obliterates the need for using libraries like D3 or Chart.js if you need simple charts, not very fancy ones.

How about calling chart update function inside of phx:update?

$(document).on('phx:update', () => {
  # fetching proper tag and calling .update() goes here
}

LiveView has proper JS support now via Hooks: https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#module-js-interop-and-client-controlled-dom

2 Likes