Contour Animation

Author Update time

This example shows how to create 2D colored contour animation from series of SWMF outputs using Matplotlib.

  • Time-dependent title.
  • Tweakable colorbar.
  • Plotting streamline on top of colored contours is a bit hacky because there is currently no intrinsic methods for removing the streamlines. Our solution here is to dispatch a streamline into lines and arrows and then remove them separately.
using Batsrus, PyPlot, Printf, PyCall

const fontsize = 14

"""
    animate(files::Vector{String}; kwargs...)

Generate figures of colored contour, optionally with streamlines.

# Keywords
- `var`: variable to plot with pcolormesh.
- `vmin`: minimum plotting value.
- `vmax`: maximum plotting value.
- `plotrange`: 2D plotting spatial range.
- `plotinterval`: spatial sampling interval.
- `cmap`: colormap accepted by Matplotlib.
- `streamvars`: if set, add streamlines to the colored contour.
- `density`: streamline density.
- `orientation`: colorbar orientation, "horizontal" or "vertical".
- `extend`: colorbar extension, "neither", "both", "min", "max".
- `use_conventional_notation`: if true, denote the vertical axis as "z".
- `broken_streamlines`: if true, streamlines can be broken.
- `overwrite`: if true, overwrite the existing image files.
- `filedir`: source data directory.
- `outdir`: output image directory.
"""
function animate(files::Vector{String}; var="Uy", vmin=-Inf, vmax=Inf,
   plotrange=[-Inf, Inf, -Inf, Inf], plotinterval=Inf,
   cmap=matplotlib.cm.RdBu_r, streamvars=nothing, density=1, orientation="horizontal",
   extend="neither", use_conventional_notation=false, broken_streamlines=false,
   overwrite=false, filedir="./", outdir="out/")

   bd = load(filedir*files[1])

   nfile = length(files)
   if isnan(vmin+vmax)
      vmin = isinf(vmin) ? minimum(v) : vmin
      vmax = isinf(vmax) ? maximum(v) : vmax
   end

   norm = matplotlib.colors.Normalize(vmin, vmax)

   filetype =
      if occursin("region", files[1])
         :PC
      else
         :GM
      end

   if use_conventional_notation # switch y and z axis
      if var[2] == 'y'
         cb_str = 
            if startswith(var, "U")
               "Uz [km/s]"
            elseif startswith(var, "B")
               "Bz [nT]"
            elseif startswith(var, "E")
               L"Ez [$\mu\mathrm{V}/\mathrm{m}$]"
            else
               var
            end
      elseif var[2] == 'z'
         cb_str = 
            if startswith(var, "U")
               "Uy [km/s]"
            elseif startswith(var, "B")
               "By [nT]"
            elseif startswith(var, "E")
               L"Ey [$\mu\mathrm{V}/\mathrm{m}$]"
            else
               var
            end
      end
   else
      cb_str = 
         if startswith(var, "U")
            var * " [km/s]"
         elseif startswith(var, "B")
             var * " [nT]"
         elseif startswith(var, "E")
            var * L" [\mu\mathrm{V}/\mathrm{m}]"
         else
            var
         end
   end

   fig = plt.figure(figsize=(4, 8), constrained_layout=true)
   ax = plt.axes()
   ax.set_xlabel(L"x [$\mathrm{R}_\mathrm{E}$]"; fontsize)
   ax.set_ylabel(L"y [$\mathrm{R}_\mathrm{E}$]"; fontsize)

   @info "1 out of $nfile"
   x, y, w = Batsrus.getdata2d(bd, var, plotrange, plotinterval; innermask=false)
   c = ax.pcolormesh(x, y, w; norm, cmap)
   ax.set_aspect("equal", adjustable="box", anchor="C")

   if orientation == "horizontal"
      cb = colorbar(c; ax, orientation, location="top", aspect=50, extend)
   else
      cb = colorbar(c; ax, orientation, extend)  
   end
   cb.ax.set_ylabel(cb_str; fontsize)
   #cb.ax.set_title(cb_str; fontsize)
   title_str = @sprintf "t = %4.1f s" bd.head.time
   ax.set_title(title_str)

   if bd.head.gencoord
      if !isnothing(streamvars)
         xi, yi, v1, v2 = get_vector(bd, streamvars, plotrange, plotinterval)
         st = ax.streamplot(xi, yi, v1, v2; color="gray", density)
      end
   else
      if !isnothing(streamvars)
         st = streamplot(bd, streamvars, ax; color="gray", density, broken_streamlines)
      end
   end

   savefig(outdir*lpad(1, 4, '0')*".png", bbox_inches="tight", dpi=200)

   if !isnothing(streamvars) # Clean up streamlines
      st.lines.remove()
      for art in ax.get_children()
         if !pybuiltin(:isinstance)(art, matplotlib.patches.FancyArrowPatch)
            continue
         end
         art.remove()
      end
   end

   for i in 2:nfile
      @info "$i out of $nfile"
      outname = outdir*lpad(i, 4, '0')*".png"
      if !overwrite
         isfile(outname) && continue
      end
      bd = load(joinpath(filedir, files[i]))
      if bd.head.gencoord
         _, _, wi = getdata2d(bd, var, plotrange, plotinterval; innermask=false)
      else
         wi = bd[var]'
      end

      c.set_array(wi)

      if !isnothing(streamvars)
         if bd.head.gencoord
            xi, yi, v1, v2 = get_vector(bd, streamvars, plotrange, plotinterval)
            st = ax.streamplot(xi, yi, v1, v2; color="gray", density)
         else
            st = streamplot(bd, "Bx;By", ax; color="gray", density, broken_streamlines)
         end
      end

      title_str = @sprintf "t = %4.1f s" bd.head.time
      ax.set_title(title_str)
   
      savefig(outname, bbox_inches="tight", dpi=200)
      if !isnothing(streamvars) # Clean up streamlines
         st.lines.remove()
         for art in ax.get_children()
            if !pybuiltin(:isinstance)(art, matplotlib.patches.FancyArrowPatch)
               continue
            end
            art.remove()
         end
      end
   end
   
   close()
end

function get_vector(bd, var, plotrange, plotinterval)
   x, w = bd.x, bd.w
   X, Y = eachslice(x, dims=3)
   X, Y = vec(X), vec(Y)
   varstream = split(var, ";")
   var1_ = findfirst(x->lowercase(x)==lowercase(varstream[1]), bd.head.wname)
   var2_ = findfirst(x->lowercase(x)==lowercase(varstream[2]), bd.head.wname)
   # Create grid values first.
   xi = range(Float64(plotrange[1]), stop=Float64(plotrange[2]), step=plotinterval)
   yi = range(Float64(plotrange[3]), stop=Float64(plotrange[4]), step=plotinterval)

   tr = matplotlib.tri.Triangulation(X, Y)
   Xi, Yi = Batsrus.meshgrid(xi, yi)

   W1 = @views w[:,:,var1_] |> vec
   interpolator = matplotlib.tri.LinearTriInterpolator(tr, W1)
   v1 = interpolator(Xi, Yi)

   W2 = @views w[:,:,var2_] |> vec
   interpolator = matplotlib.tri.LinearTriInterpolator(tr, W2)
   v2 = interpolator(Xi, Yi)

   xi, yi, v1, v2
end

#######################
filedir = "GM/"

files = filter(file -> startswith(file, "z") && endswith(file, ".out"), readdir(filedir))

var = "Bz"
vmin = -10
vmax = 10
cmap = matplotlib.cm.RdBu_r
streamvars = "Bx;By"
density = 1
orientation = "vertical"
outdir = "out/"
extend = "both"
use_conventional_notation = true

animate(files; filedir, outdir, var, vmin, vmax, orientation, cmap, streamvars, density,
   extend, use_conventional_notation)

This page was generated using DemoCards.jl.