This page describes functions that can be used in conjunction with Agents.jl to animate and interact with agent based models.
The animation at the start of this page was done by running the
examples/daisyworld.jl file, and see also an example application in Agents.jl docs.
abm_plot(model::ABM; kwargs...) → fig, abmstepper abm_plot!(ax::Axis/Axis3, model::ABM; kwargs...) → 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
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). Notice that models with
DiscreteSpace are plotted starting from 0 to n, with n the space size along each dimension.
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.
Agent related keywords
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 # am = :diamond am(a) = a.status == :S ? :circle : a.status == :I ? :diamond : :rect
Notice that for 2D models,
amcan be/return a
Polygoninstance, 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
asis meaningless, as each polygon has its own size. Use the functions
scale, rotate2Dto transform this polygon.
3D models currently do not support having different markers. As a result,
amcannot be a function. It should be a
Meshor 3D primitive (such as
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 tuple to be added to the agent's position (which matters only if there is overlap).
scatterkwargs = (): Additional keyword arguments propagated to the
Preplot related keywords
heatarray = nothing: A keyword that plots a heatmap over the space. Its values can be standard data accessors given to functions like
run!, i.e. either a symbol (directly obtain model property) or a function of the model. The returned data must be a matrix of the same size as the underlying space. For example
heatarray = :temperatureis used in the Daisyworld example. But you could also define
f(model) = create_some_matrix_from_model...and set
heatarray = f. The heatmap will be updated automatically during model evolution in videos and interactive applications.
heatkwargs = NamedTuple(): Keywords given to
heatarrayis not nothing.
static_preplot!: A function
f(ax, model)that plots something after the heatmap but before the agents. Notice that you can still make objects of this plot be visible above the agents using a translation in the third dimension like below:
function static_preplot!(ax, model) obj = scatter!(ax, [50 50]; color = :red) # Show position of teacher hidedecorations!(ax) # hide tick labels etc. translate!(obj, 0, 0, 5) # be sure that the teacher will be above students end
Figure related keywords
These only matter for
abm_plot and not for
resolution = (600, 600): Resolution of the figure.
backgroundcolor = DEFAULT_BG: Background color of the figure.
axiskwargs = NamedTuple(): Keyword arguments given to the main axis creation for e.g. setting
xticksvisible = false.
aspect = DataAspect(): The aspect ratio behavior of the axis.
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
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
The application has three buttons:
- "step": advances the simulation once for
- "run": starts/stops the continuous evolution of the model.
- "reset": 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, aspect, scatterkwargs: propagated to
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 and applicable keywords are propagated.
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.
title = "": The title of the figure.
showstep = true: If current step should be shown in title.
kwargs...: All other keywords are propagated to
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
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.
fig, 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, aspect, scatterkwargs: propagated to
adata, mdata: Same as the keyword arguments of
Agents.run!, 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.
It is possible to inspect agents at a given position by hovering the mouse cursor over the scatter points in the agent plot. A tooltip will appear which by default provides the name of the agent type, its
pos, and all other fieldnames together with their current values. This is especially useful for interactive exploration of micro data on the agent level.
For this functionality, we draw on the powerful features of Makie's
The tooltip can be customized both with regards to its content and its style by extending a single function and creating a specialized method for a given
Convert agent data into a string which is used to display all agent variables and their values in the tooltip on mouse hover. Concatenates strings if there are multiple agents at one position.
Custom tooltips for agents can be implemented by adding a specialised method for
function InteractiveDynamics.agent2string(agent::SpecialAgent) """ ✨ SpecialAgent ✨ ID = $(agent.id) Main weapon = $(agent.charisma) Side weapon = $(agent.pistol) """ end
Tracking model variables is already made easy by adding them to the
using Agents using Statistics using InteractiveDynamics using GLMakie # initialise model model, agent_step!, model_step! = Models.schelling() # define a parameter slider params = Dict(:min_to_be_happy => 1:1:5) # define data to collect and plot adata= [(:mood, mean)] # open the interactive app fig, adf, mdf = abm_data_exploration(model, agent_step!, model_step!, params; adata)
This will always display the data as scatterpoints connected with lines. In cases where more granular control over the displayed plots is needed, we need to take a few extra steps. Makie plots have to know which changes in the underlying data to watch. This is done by using
Observables. We can simply add the variable in question as an
Observable and update it after each simulation step. This can be done by adding a new stepping function which wraps the original
model_step! function and the updating of the
For the sake of a simple example, let's assume we want to add a barplot showing the current amount of happy and unhappy agents in our Schelling segregation model.
# add the new variable as an observable happiness = [count(a.mood == false for a in allagents(model)), count(a.mood == true for a in allagents(model))] |> Observable # update its value after each model step function new_model_step!(model; happiness = happiness) model_step!(model) happiness = [count(a.mood == false for a in allagents(model)), count(a.mood == true for a in allagents(model))] end # open the interactive app and use the enhanced stepping function as an argument fig, adf, mdf = abm_data_exploration(model, agent_step!, new_model_step!, params; adata) # add the desired plot to a newly created column on the right barplot(fig[:,3], [0,1], happiness; bar_labels = ["Unhappy", "Happy"]) # as usual, we can also style this new plot to our liking hidexdecorations!(current_axis())