GnuplotEx.Channel (gnuplot_ex v0.5.0)

Phoenix Channel for real-time plot streaming.

Enables pushing rendered plots to connected clients via WebSocket. Includes built-in rate limiting for backpressure control.

Setup

  1. Add the channel to your socket:

    defmodule MyAppWeb.UserSocket do use Phoenix.Socket

    channel "gnuplot:*", GnuplotEx.Channel

    # ... socket implementation end

  2. Start the RateLimiter in your supervision tree (or let GnuplotEx.Application do it):

    children = [ GnuplotEx.Channel.RateLimiter ]

Client Messages

  • "render" - Request a plot render: {plot_id, plot_spec, format}
  • "subscribe" - Subscribe to plot updates: {plot_ids: [...]}
  • "unsubscribe" - Unsubscribe from updates: {plot_ids: [...]}

Server Messages

  • "plot:update" - Pushed plot update {plot_id, format, data, timestamp}
  • "plot:rendered" - Response to render request {plot_id, format, data, render_time_ms}
  • "plot:error" - Render error {plot_id, reason, message}
  • "plot:rate_limited" - Backpressure signal {plot_id, retry_after_ms}

Example: Server-side streaming

# Push a pre-rendered plot to all subscribers
GnuplotEx.Channel.push_plot("dashboard", "sensor-1", svg_data, :svg)

# Render and stream a plot
plot = GnuplotEx.scatter(data) |> GnuplotEx.title("Live Data")
GnuplotEx.Channel.stream_plot("dashboard", "cpu-usage", plot, :svg)

Example: Client-side (JavaScript)

let channel = socket.channel("gnuplot:dashboard", {})

channel.on("plot:update", ({plot_id, data, format}) => {
  document.getElementById(plot_id).innerHTML = data
})

channel.join()

// Request a specific render
channel.push("render", {plot_id: "chart1", format: "svg"})

Summary

Functions

Handle render request from client.

Join a gnuplot topic.

Push a pre-rendered plot to all subscribers on a topic.

Stream multiple plots in parallel.

Render a plot and stream it to subscribers.

Functions

child_spec(init_arg)

handle_in(binary, map, socket)

Handle render request from client.

Expects: %{"plot_id" => id, "format" => fmt, "spec" => plot_spec}

The plot_spec should be a serializable representation of the plot. For security, this only supports pre-registered plot builders.

join(arg, params, socket)

Join a gnuplot topic.

Topics follow the pattern gnuplot:<namespace> where namespace can be any string identifier (e.g., "dashboard", "user:123").

push_plot(topic, plot_id, data, format)

@spec push_plot(String.t(), String.t(), binary(), atom()) :: :ok | {:error, term()}

Push a pre-rendered plot to all subscribers on a topic.

This is the most efficient way to stream plots when you've already rendered the output.

Parameters

  • topic - The gnuplot topic (e.g., "dashboard")
  • plot_id - Unique identifier for this plot
  • data - The rendered plot data (SVG string, PNG binary, etc.)
  • format - The format atom (:svg, :png, etc.)

Example

svg = GnuplotEx.render(plot, :svg)
GnuplotEx.Channel.push_plot("dashboard", "sensor-1", svg, :svg)

start_link(triplet)

stream_many(topic, plot_tuples, format, opts \\ [])

@spec stream_many(String.t(), [{String.t(), GnuplotEx.Plot.t()}], atom(), keyword()) ::
  :ok

Stream multiple plots in parallel.

Renders all plots concurrently and pushes each as it completes.

Example

plots = [
  {"cpu", cpu_plot},
  {"memory", mem_plot},
  {"disk", disk_plot}
]

GnuplotEx.Channel.stream_many("dashboard", plots, :svg)

stream_plot(topic, plot_id, plot, format, opts \\ [])

@spec stream_plot(String.t(), String.t(), GnuplotEx.Plot.t(), atom(), keyword()) ::
  :ok | {:error, term()}

Render a plot and stream it to subscribers.

Combines rendering and pushing in one call. Uses async rendering to avoid blocking the caller.

Parameters

  • topic - The gnuplot topic
  • plot_id - Unique identifier for this plot
  • plot - A GnuplotEx.Plot struct
  • format - Output format (:svg, :png, etc.)
  • opts - Render options

Options

  • :timeout - Render timeout in ms (default: 30_000)
  • All other options passed to GnuplotEx.render/3

Example

plot = GnuplotEx.scatter(sensor_data) |> GnuplotEx.title("Live Sensors")
GnuplotEx.Channel.stream_plot("dashboard", "sensors", plot, :svg)