GnuplotEx
An Elixir wrapper for Gnuplot 6+ with SVG-first output and ergonomic API design.
Ideal for data science, machine learning visualization, and scientific computing in Elixir.
Features
- SVG-first design - Default to scalable vector graphics for web applications
- Ergonomic API - Higher-level abstractions while maintaining low-level access
- Full 2D and 3D support - Scatter, line, surface, parametric plots and more
- ML/Data Science ready - Visualize datasets, model outputs, loss curves, and embeddings
- Gnuplot 6+ features - Data blocks, spider charts, parallel coordinates, and named palettes
- Stream-based - Efficient memory usage for large datasets (1M+ points)
- Named sessions - Run multiple independent gnuplot processes
- Dry mode - Test command generation without gnuplot installed
- Save script - Export reproducible .gp files
- Nx integration - Plot tensors directly from Nx
- LiveView ready - Phoenix LiveView components for real-time plotting
Requirements
- Elixir 1.18+
- Erlang/OTP 27+
- Gnuplot 6.0+
Installing Gnuplot
# Ubuntu/Debian
sudo apt install gnuplot
# macOS
brew install gnuplot
# Arch Linux
sudo pacman -S gnuplot
# Fedora
sudo dnf install gnuplotVerify your version:
gnuplot --version
# gnuplot 6.0 patchlevel 0Installation
Add gnuplot_ex to your list of dependencies in mix.exs:
def deps do
[
{:gnuplot_ex, "~> 0.2"}
]
endQuick Start
Low-level API
Direct control over gnuplot commands:
# 2D scatter plot
dataset = for x <- 1..100, do: [x, :math.sin(x / 10) + :rand.uniform()]
GnuplotEx.plot([
[:set, :term, :svg, :size, {800, 600}],
[:set, :output, "/tmp/scatter.svg"],
[:set, :title, "Scatter Plot"],
[:plot, "-", :with, :points, :pt, 7]
], [dataset])
# 3D surface
GnuplotEx.plot([
[:set, :term, :svg],
[:set, :output, "/tmp/surface.svg"],
[:splot, 'sin(x)*cos(y)']
])
# Named sessions - run multiple independent gnuplot processes
GnuplotEx.plot(:analysis, commands, data)
GnuplotEx.plot(:realtime, other_commands, other_data)
GnuplotEx.sessions() # => [:analysis, :realtime, :default]
# Dry mode for testing (no gnuplot required)
GnuplotEx.plot(commands, data, dry: true)
# => {:dry, %{commands: [...], script: "..."}}
# Inspect the command spec before execution
specs = GnuplotEx.build_specs(commands, data)
IO.inspect(specs)High-level API
Ergonomic pipeline-style plotting:
# Simple scatter plot
data
|> GnuplotEx.scatter(title: "My Data", color: "#E95420")
|> GnuplotEx.to_svg("/tmp/plot.svg")
# Multiple datasets
GnuplotEx.new()
|> GnuplotEx.title("Comparison")
|> GnuplotEx.scatter(data1, label: "Experiment")
|> GnuplotEx.line(data2, label: "Baseline")
|> GnuplotEx.x_label("Time")
|> GnuplotEx.y_label("Value")
|> GnuplotEx.render(:svg)
# 3D surface from function
GnuplotEx.surface(fn x, y -> :math.sin(x) * :math.cos(y) end,
x_range: -5..5,
y_range: -5..5,
palette: :viridis
)
|> GnuplotEx.to_svg("/tmp/surface.svg")
# Keyword abbreviations for compact syntax
GnuplotEx.scatter(data, t: "Plot", xr: 0..100, yr: -1..1, xl: "X", yl: "Y")
# Save reproducible gnuplot script
plot
|> GnuplotEx.to_svg("/tmp/plot.svg")
|> GnuplotEx.save_script("/tmp/plot.gp") # Can re-run with: gnuplot plot.gpGnuplot 6 Features
Spider/Radar Charts
stats = [
%{name: "Warrior", speed: 6, power: 9, defense: 8, magic: 2, luck: 5},
%{name: "Mage", speed: 5, power: 3, defense: 4, magic: 10, luck: 6}
]
GnuplotEx.spider(stats,
axes: [:speed, :power, :defense, :magic, :luck],
title: "Character Comparison"
)
|> GnuplotEx.render(:svg)Parallel Coordinates
cars = [
[25000, 30, 180, 1500],
[35000, 25, 220, 1800],
[45000, 20, 300, 2000]
]
GnuplotEx.parallel(cars,
axes: ["Price", "MPG", "HP", "Weight"],
title: "Car Comparison"
)
|> GnuplotEx.render(:svg)Named Color Palettes
GnuplotEx.surface(data, palette: :viridis)
GnuplotEx.surface(data, palette: :magma)
GnuplotEx.surface(data, palette: :plasma)
GnuplotEx.surface(data, palette: :inferno)Machine Learning & Data Science
Training Loss Curves
# Plot training progress
GnuplotEx.new()
|> GnuplotEx.title("Model Training")
|> GnuplotEx.line(train_losses, label: "Training Loss", color: "#E95420")
|> GnuplotEx.line(val_losses, label: "Validation Loss", color: "#0066CC")
|> GnuplotEx.x_label("Epoch")
|> GnuplotEx.y_label("Loss")
|> GnuplotEx.to_svg("/tmp/training.svg")Confusion Matrix
# Visualize classification results
alias GnuplotEx.ML.Confusion
Confusion.plot(confusion_matrix, class_names,
normalize: true,
title: "Confusion Matrix"
)
|> GnuplotEx.render(:svg)Dataset Visualization
# 2D dataset with class labels
GnuplotEx.new()
|> GnuplotEx.scatter(class_0_points, label: "Class 0", color: "#E95420")
|> GnuplotEx.scatter(class_1_points, label: "Class 1", color: "#0066CC")
|> GnuplotEx.title("Dataset Distribution")
|> GnuplotEx.render(:svg)
# 3D point cloud (e.g., embeddings)
GnuplotEx.scatter3d(embeddings,
color_by: labels,
palette: :viridis,
title: "t-SNE Embeddings"
)Nx Tensor Support
# Plot directly from Nx tensors
# 2D matrix as surface plot
matrix = Nx.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
GnuplotEx.surface(matrix)
|> GnuplotEx.title("3D Surface from Tensor")
|> GnuplotEx.render(:svg)
# 1D tensor as line plot
loss_tensor = Nx.tensor([0.9, 0.7, 0.5, 0.3, 0.2, 0.15, 0.1])
GnuplotEx.line(loss_tensor, label: "Loss")
|> GnuplotEx.title("Loss Curve")
|> GnuplotEx.render(:svg)Feature Distributions
# Histogram of feature values
GnuplotEx.histogram(feature_values,
bins: 50,
title: "Feature Distribution",
x_label: "Value",
y_label: "Frequency"
)
# Multiple feature comparison
GnuplotEx.new()
|> GnuplotEx.histogram(feature_1, label: "Feature 1", alpha: 0.7)
|> GnuplotEx.histogram(feature_2, label: "Feature 2", alpha: 0.7)
|> GnuplotEx.render(:svg)Output Formats
GnuplotEx supports multiple output terminals:
| Format | Use Case |
|---|---|
:svg | Web, scalable (default) |
:png | Raster images |
:pdf | Documents |
:canvas | HTML5 interactive |
:wxt / :qt | Desktop interactive |
plot
|> GnuplotEx.render(:svg) # Returns SVG string
|> GnuplotEx.to_svg(path) # Writes to file
|> GnuplotEx.to_png(path) # PNG output
|> GnuplotEx.show() # Interactive windowConfiguration
# config/config.exs
config :gnuplot_ex,
default_terminal: :svg,
svg_options: [:enhanced, size: {800, 600}],
default_palette: :viridis,
timeout: 10_000Error Handling
case GnuplotEx.plot(commands, datasets) do
{:ok, output} ->
# Success
{:error, :gnuplot_not_found} ->
# Gnuplot binary not in PATH
{:error, :gnuplot_version_unsupported} ->
# Gnuplot version < 6.0
{:error, {:command_error, line, message}} ->
# Gnuplot rejected a command
endPhoenix LiveView Integration
Add Phoenix LiveView to your dependencies:
{:phoenix_live_view, "~> 1.0"}
Use the live_gnuplot/1 component for real-time plotting:
defmodule MyAppWeb.ChartLive do
use Phoenix.LiveView
import GnuplotEx.LiveView.Component
def render(assigns) do
~H"""
<.live_gnuplot plot={@plot} width={1200} height={600} />
"""
end
def mount(_params, _session, socket) do
plot = GnuplotEx.new()
|> GnuplotEx.line(initial_data())
{:ok, assign(socket, plot: plot)}
end
def handle_info({:new_data, data}, socket) do
plot = GnuplotEx.new() |> GnuplotEx.line(data)
{:noreply, assign(socket, plot: plot)}
end
endFeatures:
- Real-time plot updates
- Automatic caching for performance
- Interactive 3D controls (mouse/touch)
- SVG and PNG rendering
- Error handling with fallback content
See the LiveView Integration Guide for complete documentation and examples.
Ecosystem Integration
GnuplotEx integrates with the Elixir ML/data science ecosystem. Add optional dependencies:
{:nx, "~> 0.7", optional: true}, # Tensor support
{:explorer, "~> 0.8", optional: true} # DataFrame support (Polars backend)Nx Tensors
Plot tensors directly with automatic dimension handling:
# 1D tensor as line plot (auto x-indices)
tensor = Nx.tensor([1.0, 4.0, 2.0, 8.0, 5.0])
GnuplotEx.line(tensor, label: "Signal")
# 2D tensor as scatter/line
points = Nx.tensor([[1, 2], [3, 4], [5, 6]])
GnuplotEx.scatter(points)
# Matrix as surface plot
matrix = Nx.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
GnuplotEx.surface(matrix)Explorer DataFrames
Plot DataFrames with automatic column detection:
df = Explorer.DataFrame.new(%{x: [1, 2, 3], y: [2, 4, 6]})
GnuplotEx.scatter(df, label: "Data")
GnuplotEx.line(df, x: :x, y: :y) # Explicit columnsML Visualization Helpers
Pre-built helpers for common ML visualizations:
alias GnuplotEx.ML.{Loss, Confusion, ROC, Embeddings}
# Training curves
Loss.plot(train_loss, val_loss, title: "Training Progress")
# Confusion matrix
Confusion.plot(matrix, ["Cat", "Dog", "Bird"], normalize: true)
# ROC curves
ROC.plot(fpr, tpr, auc: 0.87)
# Embedding visualization
Embeddings.plot(tsne_points, labels, label_names: ["A", "B", "C"])See the Ecosystem Integration Guide for complete documentation.
Performance
GnuplotEx handles large datasets efficiently with binary mode and parallel rendering.
See Benchmarks for details. Run mix bench to generate charts on your system.
Documentation
Contributing
Contributions are welcome!
# Clone and setup
git clone https://gitlab.com/tristanperalta/gnuplot_ex
cd gnuplot_ex
mix deps.get
# Run tests (requires Gnuplot 6+ installed)
mix test
# Run only tests that don't require gnuplot
mix test --exclude gnuplot
# Run credo and dialyzer
mix credo
mix dialyzerLicense
MIT License