Can I define Javascript functions on the backend and pass them to my Hook?

I am trying to setup an interactive visualization in Phoenix LiveView using Apache Echarts as the Javascript side; specifically, a deeply nested tree-map.

What I have done is to

  1. create a EchartsHook, which is in turn used in
  2. a echart(assigns) functional component
  # Apache ECharts

  attr :id, :string, required: true
  attr :chart_option, :map
  attr :width, :string, default: "600px"
  attr :height, :string, default: "400px"
  attr :class, :string, default: "grid justify-center"

  def echart(assigns) do
    ~H"""
    <div id={@id} phx-hook="EchartsHook" class={@class}>
      <div id={@id <> "-chart"} style={"width: #{@width}; height: #{@height};"} phx-update="ignore" />
      <div id={@id <> "-data"} hidden><%= Jason.encode!(@chart_option) %></div>
    </div>
    """
  end

I pass @chart_option in as an elixir map:

  def treemap() do
    %{
      series: %{
        name: "foobar",
        type: "treemap",
        visibleMin: 1,
        label: %{
          show: true
        },
        upperLabel: %{
          show: true,
          height: 30
        },
        data: load_json()
      }
    }
  end

Up to now, this is all fine and dandy. However, to get fine control over the visualization, Echarts uses javascript functions, like formatter here:

      label: {
        color: '#000',
        textBorderColor: '#fff',
        textBorderWidth: 2,
        formatter: function (param) {
          var depth = param.treePathInfo.length;
          if (depth === 2) {
            return 'radio';
          } else if (depth === 3) {
            return 'tangential';
          } else if (depth === 4) {
            return '0';
          }
          return '';
        }
      },

Question 1: Is there a way to write this chunk of javascript within the Elixir treemap() function for the JSON to decode?


I suppose the alternative approach is to hard-code what would be @chart_options in a specialized hook. Given that this is a special one-off display, that is acceptable to me. However, in this approach…

Question 2: how could one access (Elixir functions) treemap() from (Javascript) mounted()?

You can push an event from the server and handle it in the hook, or you can push an event from the client and handle it in the server.

def update(assigns, socket) do
    socket = assign(socket, assigns)

    {:ok,
     push_event(socket, "echarts:#{socket.assigns.id}:init", %{
       "option" => assigns.option,
       "merge" => assigns[:merge] || true
     })}
  end

def render(assigns) do
    ~F"""
    <div phx-hook="Echarts" id={"echarts-#{@id}"} phx-update="ignore" class={@class} data-id={@id} />
    """
  end

In your mounted callback in the hook, add an event listener for that event and merge the options, e.g

this.handleEvent(`echarts:${this.props.id}:init`, ({ option, merge }) => {
      ....
        if (option) {
            chart.setOption(option, !merge)
            chart.resize()
        }
    })

I recall encountering the hurdle of the JavaScript formatters but I haven’t done anything with it yet. I notice I have written a formatter on the JS side (in the hook) but am not using it.