Powered by AppSignal & Oban Pro

Frame Groups and Animation Modes

07_frame_groups_animation_modes.livemd

Frame Groups and Animation Modes

Mix.install([
  {:plotly_ex, "~> 0.3"},
  {:kino, "~> 0.18"}
])

Section

frame.group tags frames so you can play only a named group. animationOpts.mode controls playback:

  • "immediate" — jump to frame instantly (stop current animation)
  • "afterall" — wait for current animation to finish
  • "next" — enqueue after the currently playing group
alias Plotly.{Figure, Scatter}

# Group A: expanding circle
group_a =
  for r <- 1..20 do
    theta = Enum.map(0..36, fn i -> i * 10 * :math.pi / 180 end)
    x = Enum.map(theta, fn t -> r * :math.cos(t) end)
    y = Enum.map(theta, fn t -> r * :math.sin(t) end)
    %{name: "a#{r}", group: "circle", data: [%{x: x, y: y}]}
  end

# Group B: Lissajous figure
group_b =
  for phase <- 0..19 do
    t = Enum.map(0..100, fn i -> i * 2 * :math.pi / 100 end)
    x = Enum.map(t, fn ti -> :math.sin(2 * ti + phase * 0.3) end)
    y = Enum.map(t, fn ti -> :math.sin(3 * ti) end)
    %{name: "b#{phase}", group: "lissajous", data: [%{x: x, y: y}]}
  end

Figure.new(
  data: [Scatter.new(x: [0], y: [0], mode: "lines")],
  layout: %{
    title: "Frame Groups — Circle vs Lissajous",
    xaxis: %{range: [-21, 21], autorange: false},
    yaxis: %{range: [-21, 21], autorange: false, scaleanchor: "x"},
    updatemenus: [%{
      type: "buttons",
      buttons: [
        %{label: "Circle", method: "animate",
          args: [Enum.map(group_a, & &1.name), %{frame: %{duration: 80}}]},
        %{label: "Lissajous", method: "animate",
          args: [Enum.map(group_b, & &1.name), %{frame: %{duration: 100}}]}
      ]
    }]
  },
  frames: group_a ++ group_b
)
|> Plotly.show()