Plotting and interactive application

Plotting and interaction functionality comes from InteractiveDynamics, another package of JuliaDynamics, which uses Makie.jl.

Plotting, and the interactive application of Agents.jl, are model-agnostic and simple to use. Defining simple functions that map agents to colors, and shapes, is the only thing you need to do. If you have already defined an ABM and functions for stepping the model, you typically need to write only an extra couple of lines of code to get your visualizations going.

You need to install both InteractiveDynamics, as well as a plotting backend (we recommend GLMakie) to use the following functions.

The version of InteractiveDynamics used in the docs is:

using Pkg
Status `~/work/Agents.jl/Agents.jl/docs/Project.toml`
  [ec714cd0] InteractiveDynamics v0.13.4

Some parts of Agents.jl cannot be plotted yet in Makie.jl, and therefore alternatives are provided. However in the near future we hope to have moved everything to plotting with Makie.jl and not necessitate usage of Plots.jl or other libraries.


The following functions allow you to plot an ABM, animate it via play/pause buttons, or directly export the time evolution into a video. At the moment these functions support 2D continuous and discrete space.

abm_plot(model::ABM; kwargs...) → fig, abmstepper

Plot an agent based model by plotting each individual agent as a marker and using the agent's position field as its location on the plot. Requires Agents.

Return the overarching fig object, as well as a struct abmstepper that can be used to interactively animate the evolution of the ABM and combine it with other subplots. The figure is not displayed by default, you need to either return fig as a last statement in your functions or simply call display(fig). To progress the ABM plot n steps simply do:

Agents.step!(abmstepper, model, agent_step!, model_step!, n)

You can still call this function with n=0 to update the plot for a new model, without doing any stepping. From fig you can obtain the plotted axis (to e.g. turn off ticks, etc.) using ax = content(fig[1, 1]). See Sugarscape for an example of using abmstepper to make an animation of evolving the ABM and a heatmap in parallel with only a few lines of code.


  • ac, as, am: These three keywords decided the color, size, and marker, that each agent will be plotted as. They can each be either a constant or a function, which takes as an input a single argument and ouputs the corresponding value. For example:
    # ac = "#338c54"
    ac(a) = a.status == :S ? "#2b2b33" : a.status == :I ? "#bf2642" : "#338c54"
    # as = 10
    as(a) = 10*randn() + 1
    # as = :diamond
    as(a) = a.status == :S ? :circle : a.status == :I ? :diamond : :rect
    Notice that am can be/return a Polygon instance, which plots each agent as an arbitrary polygon. It is assumed that the origin (0, 0) is the agent's position when creating the polygon. In this case, the keyword as is meaningless, as each polygon has its own size. Use the functions scale, rotate2D to transform this polygon.
  • scheduler = model.scheduler: decides the plotting order of agents (which matters only if there is overlap).
  • offset = nothing: If not nothing, it must be a function taking as an input an agent and outputting an offset position vector to be added to the agent's position (which matters only if there is overlap).
  • equalaspect = true: Whether the plot should be of equal aspect ratio.
  • scatterkwargs = (): Additional keyword arguments propagated to the scatter plot. If am is/returns Polygons, then these arguments are propagated to a poly plot.
  • resolution = (600, 600): Resolution of the fig.
abm_play(model, agent_step!, model_step!; kwargs...) → fig, abmstepper

Launch an interactive application that plots an agent based model and can animate its evolution in real time. Requires Agents.

The agents are plotted exactly like in abm_plot, while the two functions agent_step!, model_step! decide how the model will evolve, as in the standard approach of Agents.jl and its step! function.

The application has two buttons: "run" and "reset" which starts/stops the time evolution and resets the model to its original configuration. Two sliders control the animation speed: "spu" decides how many model steps should be done before the plot is updated, and "sleep" the sleep() time between updates.


  • ac, am, as, scheduler, offset, equalaspect, scatterkwargs: propagated to abm_plot.
  • spu = 1:100: The values of the "spu" slider.
abm_video(file, model, agent_step! [, model_step!]; kwargs...)

This function exports the animated time evolution of an agent based model into a video saved at given path file, by recording the behavior of abm_play (without sliders). The plotting is identical as in abm_plot.


  • ac, am, as, scheduler, offset, equalaspect, scatterkwargs: propagated to abm_plot.
  • spf = 1: Steps-per-frame, i.e. how many times to step the model before recording a new frame.
  • framerate = 30: The frame rate of the exported video.
  • frames = 300: How many frames to record in total, including the starting frame.
  • resolution = (600, 600): Resolution of the fig.
  • axiskwargs = NamedTuple(): Keyword arguments given to the main axis creation for e.g. setting xticksvisible = false.

Interactive application

abm_data_exploration(model::ABM, agent_step!, model_step!, params=Dict(); kwargs...)

Open an interactive application for exploring an agent based model and the impact of changing parameters on the time evolution. Requires Agents.

The application evolves an ABM interactively and plots its evolution, while allowing changing any of the model parameters interactively and also showing the evolution of collected data over time (if any are asked for, see below). The agent based model is plotted and animated exactly as in abm_play, and the arguments model, agent_step!, model_step! are propagated there as-is.

Calling abm_data_exploration returns: figure, agent_df, model_df. So you can save the figure, but you can also access the collected data (if any).


Besides the basic time evolution interaction of abm_play, additional functionality here allows changing model parameters in real time, based on the provided fourth argument params. This is a dictionary which decides which parameters of the model will be configurable from the interactive application. Each entry of params is a pair of Symbol to an AbstractVector, and provides a range of possible values for the parameter named after the given symbol (see example online). Changing a value in the parameter slides is only updated into the actual model when pressing the "update" button.

The "reset" button resets the model to its original agent and space state but it updates it to the currently selected parameter values. A red vertical line is displayed in the data plots when resetting, for visual guidance.


  • ac, am, as, scheduler, offset, equalaspect, scatterkwargs: propagated to abm_plot.
  • adata, mdata: Same as the keyword arguments of!, and decide which data of the model/agents will be collected and plotted below the interactive plot. Notice that data collection can only occur on plotted steps (and thus steps not plotted due to "spu" are also not data-collected).
  • alabels, mlabels: If data are collected from agents or the model with adata, mdata, the corresponding plots have a y-label named after the collected data. Instead, you can give alabels, mlabels (vectors of strings with exactly same length as adata, mdata), and these labels will be used instead.
  • when = true: When to perform data collection, as in!.
  • spu = 1:100: Values that the "spu" slider will obtain.

Here is an example application made with InteractiveDynamics.abm_data_exploration.

the application is made with the following script:

using InteractiveDynamics, Agents, Random, Statistics
import GLMakie

Random.seed!(165) # hide
model, agent_step!, model_step! = Models.daisyworld(;
    solar_luminosity = 1.0, solar_change = 0.0, scenario = :change
Daisy, Land = Agents.Models.Daisy, Agents.Models.Land

# Parameter define agent color and shape:
using GLMakie.AbstractPlotting: to_color
daisycolor(a::Daisy) = RGBAf0(to_color(a.breed))
landcolor = cgrad(:thermal)
daisycolor(a::Land) = to_color(landcolor[(a.temperature+50)/150])

daisyshape(a::Daisy) = '♣'
daisysize(a::Daisy) = 15
daisyshape(a::Land) = :rect
daisysize(a::Land) = 20
landfirst = by_type((Land, Daisy), false) # scheduler

# Parameter exploration and data collection:
params = Dict(
    :solar_change => -0.1:0.01:0.1,
    :surface_albedo => 0:0.01:1,

black(a) = a.breed == :black
white(a) = a.breed == :white
daisies(a) = a isa Daisy
land(a) = a isa Land
adata = [(black, count, daisies), (white, count, daisies), (:temperature, mean, land)]
mdata = [:solar_luminosity]

alabels = ["black", "white", "T"]
mlabels = ["L"]

model, agent_step!, model_step! = Models.daisyworld(; solar_luminosity = 1.0, solar_change = 0.0, scenario = :change)

fig, adf, mdf = abm_data_exploration(
    model, agent_step!, model_step!, params;
    ac = daisycolor, am = daisyshape, as = daisysize,
    mdata, adata, alabels, mlabels,
    scheduler = landfirst # crucial to change model scheduler!

Graph plotting

To plot agents existing of a GraphSpace we can't use InteractiveDynamics because Makie.jl does not support plotting on graphs (yet). We provide the following function in this case, which comes into scope when using Plots. See also the SIR model for the spread of COVID-19 example for an application.

abm_plot_on_graph(model::ABM{<: GraphSpace}; ac, as, am, kwargs...)

This function plots an ABM with a GraphSpace. It functions similarly with [abm_plot], but is based on Plots.jl (specifically GraphRecipes.jl).

The three key functions ac, as, am do not get an agent as an input but a vector of agents at each node of the graph. Their output is the same: the color, size, and marker type of the node.

Here as defaults to length. Internally, the graphplot recipe is used, and all other kwargs... are propagated there.


Open Street Map plotting

Plotting an open street map is also not possible with Makie.jl at the moment, but there is a Julia package that does this kind of plotting, OpenStreetMapXPlots.jl. Its usage is demonstrated in the Zombie Outbreak example page.

Plots.jl Recipes

Whilst the primary method for plotting agents models is through InteractiveDynamics, the following Plots recipes can also be used if you prefer the Plots.jl ecosystem.

Notice that these methods will emit a warning. Pass warn = false to suppress it.

plotabm(model::ABM{<: ContinuousSpace}; ac, as, am, kwargs...)
plotabm(model::ABM{<: DiscreteSpace}; ac, as, am, kwargs...)

Plot the model as a scatter-plot, by configuring the agent color, size and marker (shape) via the keywords ac, as, am. These keywords can be constants, or they can be functions, each accepting an agent and outputting a valid value for color/size/marker.

The keyword scheduler = model.scheduler decides the plotting order of agents (which matters only if there is overlap).

The keyword offset is a function with argument offest(a::Agent). It targets scenarios where multiple agents existin within a grid cell as it adds an offset (same type as agent.pos) to the plotted agent position.

All other keywords are propagated into Plots.scatter and the plot is returned.

plotabm(model::ABM{<: GraphSpace}; ac, as, am, kwargs...)

This function is the same as plotabm for ContinuousSpace, but here the three key functions ac, as, am do not get an agent as an input but a vector of agents at each node of the graph. Their output is the same.

Here as defaults to length. Internally, the graphplot recipe is used, and all other kwargs... are propagated there.

plotabm!(plt, model)

Functionally the same as plotabm, however this method appends to the active plot, or one identified as plt.