Skip to content

devstopfix/gnuplot-elixir

Repository files navigation

Gnuplot Elixir

A simple interface from Elixir data to the Gnuplot graphing utility that uses Erlang Ports to transmit data from your application to Gnuplot. Datasets are streamed directly to STDIN without temporary files and you can plot 1M points in 12.7 seconds.

Please visit the Gnuplot demos gallery to see all the possibilities, the manual which describes the grammar, and the examples folder.

This is a conversion of the Clojure Gnuplot library by aphyr. This library has been tested on OS X, Ubuntu 16.04 and CentOS 7.6.

Build Status Hex.pm API Docs Platforms

Usage

The plot function takes two arguments:

  • a list of commands (each of which is a list of terms)
  • a list of Streams or Enumerable datasets (not required when plotting functions)

Commands are lists of terms that normally start with an atom such as :set. They may be written as lists or Word lists - the following lines are equivalent:

  • [:set, :xtics, :off]
  • ~w(set xtics off)a

and both convert to set xtics off.

Strings are output inside double quotes, and charlists are output without modification. [:plot, 'sin(x)', :title, "Sine Wave"] becomes: plot sin(x) title "Sine Wave"

A dataset is a list of points, each point is a list of numbers. A dataset can be a Stream.

Scatter plot with a single dataset

Lets compare the distributions of the Erlang rand functions:

dataset = for _ <- 0..1000, do: [:rand.uniform(), :rand.normal()]
{:ok, _cmd} = Gnuplot.plot([
  [:set, :title, "rand uniform vs normal"],
  [:plot, "-", :with, :points]
  ], [dataset])

Gnuplot will by default open a window containing your plot:

rand

The command string sent (_cmd above) can be manually inspected should the chart not appear as you expected. If the chart is not drawn due to an error then the result will be {:error, cmd, errors}.

PNG of two datasets

Write two datasets to a PNG file:

import Gnuplot

{:ok, _cmd} = plot([
  [:set, :term, :pngcairo],
  [:set, :output, "/tmp/rand.png"],
  [:set, :title, "rand uniform vs normal"],
  [:set, :key, :left, :top],
  plots([
      ["-", :title, "uniform", :with, :points],
      ["-", :title, "normal", :with, :points]
      ])
  ],
  [
        for(n <- 0..100, do: [n, n * :rand.uniform()]),
        for(n <- 0..100, do: [n, n * :rand.normal()])
  ])

uniform-vs-rand

When we are plotting multiple datasets in the same chart we need a comma separated list for the plot command which is made with the plots, splots or list function.

NB the :png terminal can also be used but it produces rougher output.

Plot functions without datasets

Gnuplot.plot([[:plot, 'sin(x)', :title, "Sine Wave"]])

rand

Gnuplot.plot([
        ~w(set autoscale)a,
        ~w(set samples 800)a,
        [:plot, -30..20, 'sin(x*20)*atan(x)']
])

NB ranges can be used

rand

Multiplot

The multiplot mode places serveral plots on the same page:

Gnuplot.plot([
  [:set, :multiplot, :layout, '2,1'],
  [:plot, 'sin(x)/x'],
  [:plot, 'cos(x)']
  ])

Installation

This library is available in Hex with documentation and the package can be installed by adding gnuplot to your project:

def deps do
  [
    {:gnuplot, "~> 1.22"}
  ]
end

Testing

Some tests create plots which require gnuplot to be installed. They can be be excluded with:

mix test --exclude gnuplot:true

Performance

The performance of the library on a MacBook Air is comparable to the Clojure version when gnuplot draws to a GUI. It is a little faster when writing directly to a PNG when running on a server. The times below are in milliseconds. Each plot was made in increasing order of the number of points and after a cold start of the VM. The last two columns show the refactoring from Enumerable to Streams.

Points Clojure GUI Elixir GUI Elixir PNG Elixir Enum Elixir Stream
1 1,487 5 18 4 5
10 1,397 10 1 <1 1
1e2 1,400 4 12 1 1
1e3 1,381 59 52 8 10
1e4 1,440 939 348 211 211
1e5 5,784 5,801 3,494 1,873 1,313
1e6 49,275 43,464 35,505 19,916 12,775
MacBook MacBook MacBook Ubuntu 16.04 Ubuntu 16.04
2.5 GHz i5 2.5 GHz i5 2.5 GHz i5 3.3 GHz 2vCPU 3.3 GHz 2vCPU

performance

points      = [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000]
clojure_gui = [1.487, 1.397, 1.400, 1.381, 1.440, 5.784, 49.275]
elixir_gui  = [0.005, 0.010, 0.004, 0.059, 0.939, 5.801, 43.464]
elixir_png  = [0.002, 0.010, 0.049, 0.040, 0.349, 4.091, 41.521]
ubuntu_t2m  = [0.004, 0.002, 0.001, 0.008, 0.211, 1.873, 19.916]
ubuntu_strm = [0.002, 0.001, 0.001, 0.009, 0.204, 1.279, 12.858]
datasets = for ds <- [clojure_gui, elixir_gui, elixir_png, ubuntu_t2m, ubuntu_strm], do:
  Enum.zip(points, ds)

Gnuplot.plot([
  [:set, :title, "Time to render scatter plots"],
  [:set, :xlabel, "Points in plot"],
  [:set, :ylabel, "Elapsed (s)"],
  ~w(set key left top)a,
  ~w(set logscale xy)a,
  ~w(set grid xtics ytics)a,
  ~w(set style line 1 lw 2 lc '#63b132')a,
  ~w(set style line 2 lw 2 lc '#2C001E')a,
  ~w(set style line 3 lw 2 lc '#5E2750')a,
  ~w(set style line 4 lw 2 lc '#E95420')a,
  ~w(set style line 5 lw 4 lc '#77216F')a,
  Gnuplot.plots([
    ["-", :title, "Clojure GUI", :with, :lines, :ls, 1],
    ["-", :title, "Elixir GUI", :with, :lines, :ls, 2],
    ["-", :title, "Elixir PNG", :with, :lines, :ls, 3],
    ["-", :title, "Elixir t2.m", :with, :lines, :ls, 4],
    ["-", :title, "Elixir Stream", :with, :lines, :ls, 5]
  ])],
  datasets
)

Credits and licence

Original design ©2015 Kyle Kingsbury.

Elixir code ©2022 DEVSTOPFIX LTD. Contributions from piisgaaf

Distributed under the Eclipse Public License v2.

About

Elixir interface to Gnuplot graphing utility

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages