You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Elixir、Phoenix、SurfaceUI中渲染VegaLite图表?

在Elixir、Phoenix、SurfaceUI组件中渲染VegaLite图表

问题背景

我需要在Surface LiveView组件中渲染VegaLite图表,已经能生成VegaLite的spec结构,但不知道如何把它渲染到页面上。目前的尝试代码如下:

defmodule CfiAppWeb.Dashboard do
  @moduledoc """
  LiveView for the dashboard
  """
  use Surface.LiveView
  alias VegaLite, as: Vl

  def render(assigns) do
    ~F"""
      <main class="container mx-auto p-4" >
        <div>
          <h1>This is dashboard</h1>
          <div>{render_graph()}</div>
        </div>
      </main>
    """
  end

  def render_graph() do
    Vl.new(width: 400, height: 400)
      |> Vl.mark(:line)
      |> Vl.encode_field(:x, "x", type: :quantitative)
      |> Vl.encode_field(:y, "y", type: :quantitative)
  end
end

通常在纯JS环境里可以通过DOM ID渲染Vega图表,但在Surface LiveView场景下找不到合适的方法。

解决方案

要在Surface中渲染VegaLite图表,核心是把Elixir侧生成的VegaLite spec转换成JSON,然后在前端用vega-embed库渲染到指定DOM元素。具体步骤如下:

1. 添加前端依赖

首先在项目的package.json中添加vega相关依赖:

{
  "dependencies": {
    "vega": "^5.25.0",
    "vega-embed": "^6.22.2",
    "vega-lite": "^5.12.0"
  }
}

运行npm install完成依赖安装。

2. 修改Surface组件,传递JSON格式的spec

修改LiveView代码,将VegaLite spec转换成JSON字符串,给图表容器指定唯一ID,并添加Surface的phx-hook标记触发前端渲染逻辑:

defmodule CfiAppWeb.Dashboard do
  @moduledoc """
  LiveView for the dashboard
  """
  use Surface.LiveView
  alias VegaLite, as: Vl

  def render(assigns) do
    ~F"""
      <main class="container mx-auto p-4" >
        <div>
          <h1>This is dashboard</h1>
          <div id="vega-chart" phx-hook="VegaEmbed" data-spec={Jason.encode!(render_graph())}></div>
        </div>
      </main>
    """
  end

  def render_graph() do
    Vl.new(width: 400, height: 400)
    |> Vl.data_from_values(x: 1..100, y: 1..100) # 补充数据源,原代码缺少数据配置
    |> Vl.mark(:line)
    |> Vl.encode_field(:x, "x", type: :quantitative)
    |> Vl.encode_field(:y, "y", type: :quantitative)
  end
end

3. 编写Phoenix Hook处理渲染逻辑

在assets/js/app.js中添加自定义Phoenix Hook,调用vega-embed完成图表渲染:

import { Socket } from "phoenix";
import { LiveSocket } from "phoenix_live_view";
import topbar from "../vendor/topbar";
import vegaEmbed from "vega-embed";

let Hooks = {};

Hooks.VegaEmbed = {
  mounted() {
    // 解析DOM上的spec数据并渲染图表
    const spec = JSON.parse(this.el.dataset.spec);
    vegaEmbed(this.el, spec)
      .then(result => {
        // 保存视图实例,方便后续更新
        this.view = result.view;
      })
      .catch(console.error);
  },
  updated() {
    // 当LiveView更新时,重新加载新的spec
    const spec = JSON.parse(this.el.dataset.spec);
    this.view.change(spec);
  }
};

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

// ... 保留文件中其他原有代码

4. 动态数据更新(可选)

如果需要根据LiveView的状态动态更新图表数据,可以修改组件实现数据联动:

def mount(_params, _session, socket) do
  # 初始化图表数据
  socket = assign(socket, chart_data: generate_random_data())
  {:ok, socket}
end

def render(assigns) do
  ~F"""
    <main class="container mx-auto p-4" >
      <div>
        <h1>This is dashboard</h1>
        <div id="vega-chart" phx-hook="VegaEmbed" data-spec={Jason.encode!(render_graph(@chart_data))}></div>
        <button phx-click="refresh_data" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded">刷新数据</button>
      </div>
    </main>
  """
end

def handle_event("refresh_data", _, socket) do
  # 更新图表数据
  socket = assign(socket, chart_data: generate_random_data())
  {:noreply, socket}
end

# 生成随机数据示例
defp generate_random_data() do
  1..100 |> Enum.map(fn x -> %{x: x, y: :rand.uniform(100)} end)
end

defp render_graph(data) do
  Vl.new(width: 400, height: 400)
  |> Vl.data_from_values(data)
  |> Vl.mark(:line)
  |> Vl.encode_field(:x, "x", type: :quantitative)
  |> Vl.encode_field(:y, "y", type: :quantitative)
end

点击"刷新数据"按钮时,LiveView会更新图表数据,前端Hook的updated方法会自动重新渲染图表。


内容的提问来源于stack exchange,提问作者suppa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 16:35:18