Integrating vis.js with Phoenix Live View

1 year ago

One of the projects here at Drover required us to show a directed graph representation of a flow that happens inside that service and, after looking into various alternatives, we settled on https://visjs.org/.

Since this project has a Phoenix LiveView back-office, I had to figure out how to integrate the graph drawing mechanism into the LV and the way we ended up doing that was by using the hooks mechanism to inject the data into vis.js and draw the graph after the view was mounted.

The Live View

defmodule OutstandingLittleServiceWeb.Diagrams.ShowView do
@moduledoc “””
Shows a particular diagram
“””
use OutstandingLittleServiceWeb, :live_view
alias OutstandingLittleService.Data
alias OutstandingLittleService.Utils
  def render(assigns) do
~L”””
<%= if @loading do %>
<div>Loading information…</div>
<% else %>
<h1><%= @diagram.name %></h1>
<h5><%= @diagram.uuid %></h5>
<h3><%= @diagram.description %></h3>
<div id=”diagram”
style=”width:800px; height:400px”
phx-hook=”Diagram”
data-diagram-data=”<%= Poison.encode!(Utils.diagram_to_json(@diagram)) %>”></div>
<p/>
<% end %>
“””
end
  def mount(_params, _session, socket) do
{:ok, assign(socket, loading: true)}
end
  def handle_info({:load_diagram, params}, socket) do
diagram = Data.get_diagram!(params[“id”], eager_load: true)
    {:noreply,
assign(socket,
loading: false,
diagram: diagram
)}
end
  def handle_params(params, _uri, socket) do
send(self(), {:load_diagram, params})
{:noreply, assign(socket, loading: true)}
end
end

As you can see, it’s a pretty standard LV in which i add the phx-hook “Diagram” to the diagram div and also inject the JSON structure i want as a data attribute. Utils.diagram_to_json is merely a helper function that knows how to convert a diagram to a JSON representation that vis.js can use.

LiveView hooks

On app.js i then define the hook that will inject the data and initialise the graph:

import css from “../css/app.css”
import “phoenix_html”
import { Socket } from “phoenix”
import LiveSocket from “phoenix_live_view”
import diagramInit from “./diagram”
let csrfToken = document.querySelector(“meta[name=’csrf-token’]”)
.getAttribute(“content”)
let Hooks = {}
Hooks.Diagram = {
mounted() {
let data = this.el.getAttribute(“data-diagram-data”)
diagramInit(JSON.parse(data))
}
}
let liveSocket = new LiveSocket(
“/live”,
Socket, {
hooks: Hooks,
params: {
_csrf_token: csrfToken
}
}
)
liveSocket.connect()

Initialising the graph

Finally on diagram.js the graph gets initialised:

import { DataSet, Network } from ‘vis/index-network’;
function getOptions() {
// Return vis.js options here, like layout, physics, etc
// Ommited for brevity
}
function addNode(nodes, id, label, description, result = null) {
nodes.push({
id: id,
label: “<b>” + label + “</b>”,
description: description
})
return nodes
}
function addLink(links, id, start_id, end_id, label = null) {
links.push({
id: id,
from: start_id,
to: end_id,
arrows: “to”,
physics: “false”,
label: “<b>” + label + “</b>”
})
return links
}
function diagramInit(data) {
let diagram = data
  // Setup data
let nodes = []
diagram.components.forEach(component => {
nodes = addNode(nodes, component.id, component.name, component.description, component.result)
})
  // Setup links
let links = []
diagram.links.forEach(link => {
links = addLink(links, link.id, link.start_component_id, link.end_component_id)
})
  // Setup graph
let container = document.getElementById(‘diagram’)
let data = { nodes: nodes, edges: links }
new Network(container, data, getOptions())
}
export default diagramInit

Notes

  • Vis is added to the project using “npm install vis”, which will add the entry to the package.json file for you.
  • Some code on the diagram.js file is obviously specific to my case, namely the links between the diagram components, which are used to tell vis how to connect things, but it boils down to adding the nodes each with a different id, and then telling vis the start and end id for each connection.
  • As always, your mileage may vary.

Final thoughts

Hopeful by now you should have a good idea how to integrate external JS libraries and make them work with LiveView. It’s basically just figuring out how to plug them into the JS hooks mechanism and pass the adequate data between them.

There are other ways to pass the data, data attributes was just the way that seemed the easiest. But you could probably also use events between JS and the LV to accomplish the same goal, or other strategies.

Happy Elixir'ing!


Integrating vis.js with Phoenix Live View was originally published in Drover Engineering on Medium, where people are continuing the conversation by highlighting and responding to this story.