Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

PlotMath (using VegaLite.push)

PlotMath.livemd

PlotMath (using VegaLite.push)

Section

Mix.install([
  {:kino, "~> 0.5.2"},
  {:vega_lite, "~> 0.1.3"}
])
:ok
defmodule PlotMath do
  alias VegaLite, as: Vl

  @default_precision 10
  @default_sleep_ms 1
  @default_x_min -2 * :math.pi()
  @default_x_max 2 * :math.pi()

  def plot_fun(fun, opts \\ []) do
    precision = Keyword.get(opts, :precision, @default_precision)

    x_min = opts |> calc_x_min()

    x_max = opts |> calc_x_max()

    sleep_ms = Keyword.get(opts, :sleep_ms, @default_sleep_ms)

    widget =
      Vl.new(width: 400, height: 400)
      |> Vl.mark(:line)
      |> Vl.encode_field(:x, "x", type: :quantitative)
      |> Vl.encode_field(:y, "y", type: :quantitative)
      |> Kino.VegaLite.new()
      |> Kino.render()

    xys =
      calc_xs(x_min, x_max, precision)
      |> calc_xys(fun)

    for %{x: x, y: y} <- xys do
      Kino.VegaLite.push(widget, %{x: x, y: y})
      if sleep_ms > 0, do: Process.sleep(sleep_ms), else: :ok
    end
  end

  def calc_xs(x_min, x_max, precision) do
    # e.g., to plot these points: [-6.2, -6.1, -6.0, ..., 5.9, 6.0, 6.1, 6.2],
    #       you would call `calc_xs(-6.2, 6.2, 10)`
    x_min_int = (x_min * precision) |> Kernel.floor()
    x_max_int = (x_max * precision) |> Kernel.ceil()

    x_min_int..x_max_int
    |> Enum.to_list()
    |> Enum.map(&amp;(&amp;1 / precision))
  end

  def calc_xys(xs, fun) do
    xs
    |> Enum.map(&amp;calc_xy(&amp;1, fun))
    |> Enum.filter(fn %{y: y} -> y != :error end)
  end

  def calc_xy(x, fun) do
    y =
      try do
        fun.(x)
      rescue
        ArithmeticError -> :error
      end

    %{x: x, y: y}
  end

  defp calc_x_min(opts) do
    opts
    |> Keyword.get(:x_min, @default_x_min)
    |> :erlang.float()
    |> Float.floor(1)
  end

  defp calc_x_max(opts) do
    opts
    |> Keyword.get(:x_max, @default_x_max)
    |> :erlang.float()
    |> Float.ceil()
  end
end
{:module, PlotMath, <<70, 79, 82, 49, 0, 0, 19, ...>>, {:calc_x_max, 1}}
PlotMath.plot_fun(&amp;:math.sin(&amp;1), sleep_ms: 10)
nil
nil
PlotMath.plot_fun(&amp;:math.cos(&amp;1))
nil
nil
PlotMath.plot_fun(&amp;:math.tan(&amp;1), precision: 100, sleep_ms: 1)
nil
nil
PlotMath.plot_fun(&amp;(1 / :math.tan(&amp;1)), precision: 100, sleep_ms: 1)
nil
nil
PlotMath.plot_fun(&amp;(1 / :math.sin(&amp;1)), precision: 100, sleep_ms: 1)
nil
nil
PlotMath.plot_fun(&amp;(1 / :math.cos(&amp;1)), precision: 100, sleep_ms: 1)
nil
nil
PlotMath.plot_fun(&amp;:math.log(&amp;1), precision: 100, sleep_ms: 1)
nil
nil
PlotMath.plot_fun(&amp;:math.exp(&amp;1), precision: 100, sleep_ms: 1, x_min: -3, x_max: 3)
nil
nil
PlotMath.plot_fun(&amp;:math.pow(&amp;1, 2), precision: 100, sleep_ms: 1, x_min: -3, x_max: 3)
nil
nil
PlotMath.plot_fun(&amp;:math.pow(&amp;1, 3), precision: 100, sleep_ms: 1, x_min: -3, x_max: 3)
nil
nil
PlotMath.plot_fun(&amp;:math.pow(&amp;1, 4), precision: 100, sleep_ms: 1, x_min: -4, x_max: 4)
nil
nil