Subplot Line Animation

Author Update time

This example shows how to create multi-panel 1D space line animation from a series of FLEKS outputs.

  • Twin axes sharing the same x-axis for showing two quantities in one frame.
  • Time-dependent title.
  • Fixed value ranges.

Since currently we don't have true 1D outputs, in this demo we show the way to extract the first column from 2D cuts.

using Batsrus, Printf, PyPlot

function create_figure()
   fig, axs = subplots(5, 1, figsize=(12, 8),
      sharex=true, sharey=false, constrained_layout=true)

   lw1 = 1.0
   lw2 = 1.0
   lw3 = 1.0

   xmin, xmax = -6300.0, -1300.0

   ρemin, ρemax = 0.0, 0.05 # mi/me = 400
   ρimin, ρimax = 0.0, 20.0
   vmin, vmax = -600.0, 300.0
   pmin, pmax = 1e-3, 1.0
   Bmin, Bmax = -3.0, 30.0
   Emin, Emax = -5.0, 5.0

   l11, = axs[1].plot([], [], lw=lw1)
   l211, = axs[2].plot([], [], lw=lw1, label="Uex")
   l212, = axs[2].plot([], [], lw=lw1, label="Uey")
   l213, = axs[2].plot([], [], lw=lw1, label="Uez")
   l311, = axs[3].plot([0.0, 1.0], [pmin, pmax], lw=lw1, alpha=0.8, label=L"$P_{exx}$")
   l312, = axs[3].plot([0.0, 1.0], [pmin, pmax], lw=lw1, alpha=0.8, label=L"$P_{eyy}$")
   l313, = axs[3].plot([0.0, 1.0], [pmin, pmax], lw=lw1, alpha=0.8, label=L"$P_{ezz}$")
   l41, = axs[4].plot([], [], lw=lw2, label="x")
   l42, = axs[4].plot([], [], lw=lw2, label="y")
   l43, = axs[4].plot([], [], lw=lw2, label="z")
   l51, = axs[5].plot([], [], lw=lw2, label="x")
   l52, = axs[5].plot([], [], lw=lw2, label="y")
   l53, = axs[5].plot([], [], lw=lw2, label="z")

   axs[1].set_xlim(xmin, xmax)
   axs[1].set_ylim(ρemin, ρemax)
   axs[2].set_ylim(vmin, vmax)
   axs[3].set_yscale("log")
   axs[3].set_ylim(pmin, pmax)
   axs[4].set_ylim(Bmin, Bmax) # B
   axs[5].set_ylim(Emin, Emax) # E

   for ax in axs
      ax.grid(true)
   end

   axs[4].set_ylabel("B [nT]"; fontsize)
   axs[5].set_ylabel("E [mV/m]"; fontsize)

   axs[end].set_xlabel("x [km]"; fontsize)

   axs[1].set_ylabel(L"$\rho_e$ [amu/cc]"; fontsize, color="tab:blue")
   axs[1].tick_params(axis="y", labelcolor="tab:blue")

   ax12 = axs[1].twinx()
   ax12.set_ylim(ρimin, ρimax)

   ax12.set_ylabel(L"$\rho_i$ [amu/cc]"; fontsize, color="tab:red")

   l12, = ax12.plot([], []; lw=lw1, color="tab:red", alpha=0.8)
   ax12.tick_params(axis="y", labelcolor="tab:red")

   axs[2].set_ylabel(L"$U_e$ [km/s]"; fontsize)

   ax22 = axs[2].twinx()
   ax22.set_ylim(vmin, vmax)

   ax22.set_ylabel(L"$U_i$ [km/s]"; fontsize)

   l221, = ax22.plot([], [], lw=lw3, label="Uix", color="tab:red")
   l222, = ax22.plot([], [], lw=lw3, label="Uiy", color="tab:purple")
   l223, = ax22.plot([], [], lw=lw3, label="Uiz", color="tab:brown")

   axs[3].set_ylabel(L"$P_e$ [nT]"; fontsize)

   ax32 = axs[3].twinx()
   ax32.set_ylabel(L"$P_i$ [nT]"; fontsize)

   fake_range = [0.0, 1.0]
   p_range = [pmin, pmax]
   p_alpha = 0.8
   l321, = ax32.plot(fake_range, p_range, lw=lw3, alpha=p_alpha, label=L"$P_{ixx}$",
      color="tab:red")
   l322, = ax32.plot(fake_range, p_range, lw=lw3, alpha=p_alpha, label=L"$P_{iyy}$",
      color="tab:purple")
   l323, = ax32.plot(fake_range, p_range, lw=lw3, alpha=p_alpha, label=L"$P_{izz}$",
      color="tab:brown")

   ax32.set_yscale("log")
   ax32.set_ylim(p_range...)

   leg21 = axs[2].legend(;loc=(0.34, 0.05), ncols=3, frameon=false)
   leg22 = ax22.legend(;loc=(0.0, 0.05), ncols=3, frameon=false)
   leg31 = axs[3].legend(;loc=(0.34, -0.05), ncols=3, frameon=false)
   leg32 = ax32.legend(;loc=(0.0, -0.05), ncols=3, frameon=false)
   leg4 = axs[4].legend(;loc="upper left", ncols=3, frameon=false)
   leg5 = axs[5].legend(;loc="upper left", ncols=3, frameon=false)

   # set the linewidth of each legend object
   legs = (leg21, leg22, leg31, leg32, leg4, leg5)
   for leg in legs
      for legobj in leg.legend_handles
         legobj.set_linewidth(1.5)
      end
   end

   ls = (l11, l12, l211, l212, l213, l221, l222, l223, l311, l312, l313, l321, l322, l323,
      l41, l42, l43, l51, l52, l53)

   axs, ls
end

function slice1d_avg(bd, var, dir::Int=2)
   mean(bd[var], dims=dir) |> vec
end

function animate(files::Vector{String}, axs, ls; outdir="out/", overwrite::Bool=false,
   icut::Int=1, nbox::Int=1, dir=2, doAverage::Bool=false)
   l11, l12, l211, l212, l213, l221, l222, l223, l311, l312, l313, l321, l322, l323,
      l41, l42, l43, l51, l52, l53 = ls

   for (i, file) in enumerate(files)
      @info "$i in $(length(files))"
      outname = outdir*lpad(i, 4, '0')*".png"
      if !overwrite
         isfile(outname) && continue
      end

      bd = load(joinpath(filedir, file))

      x = @views bd.x[:,1,1]

      if doAverage
         slice = let bd = bd, dir = dir, nbox = nbox
            var -> moving_average(slice1d_avg(bd, var, dir), nbox)
         end
      else
         slice = let bd = bd, dir = dir, nbox = nbox, icut = icut
            var -> moving_average(slice1d(bd, var, icut, dir), nbox)
         end
      end

      d = slice("rhos0")
      l11.set_data(x, d)
      d = slice("rhos1")
      l12.set_data(x, d)
      d = slice("Uxs0")
      l211.set_data(x, d)
      d = slice("Uys0")
      l212.set_data(x, d)
      d = slice("Uzs0")
      l213.set_data(x, d)
      d = slice("Uxs1")
      l221.set_data(x, d)
      d = slice("Uys1")
      l222.set_data(x, d)
      d = slice("Uzs1")
      l223.set_data(x, d)
      d = slice("PXXS0")
      l311.set_data(x, d)
      d = slice("PYYS0")
      l312.set_data(x, d)
      d = slice("PZZS0")
      l313.set_data(x, d)
      d = slice("PXXS1")
      l321.set_data(x, d)
      d = slice("PYYS1")
      l322.set_data(x, d)
      d = slice("PZZS1")
      l323.set_data(x, d)

      d = slice_data("Bx")
      l41.set_data(x, d)
      d = slice_data("By")
      l42.set_data(x, d)
      d = slice_data("Bz")
      l43.set_data(x, d)
      d = slice_data("Ex") ./ 1000
      l51.set_data(x, d)
      d = slice_data("Ey") ./ 1000
      l52.set_data(x, d)
      d = slice_data("Ez") ./ 1000
      l53.set_data(x, d)

      title_str = @sprintf "t = %4.1f s" bd.head.time
      axs[1].set_title(title_str; fontsize)

      savefig(outname, bbox_inches="tight", dpi=200)
   end

   return
end

#################

# Data directory
filedir = "./"

const fontsize = 16

pick_file = file -> startswith(file, "z") && endswith(file, ".out")

files = filter(pick_file, readdir(filedir))

axs, ls = create_figure()
animate(files, axs, ls; icol=1, outdir="figures/", overwrite=true)

close()

This page was generated using DemoCards.jl.