Skip to content
This repository has been archived by the owner on Jun 21, 2024. It is now read-only.

Commit

Permalink
Agent inspection on mouse hover (#72)
Browse files Browse the repository at this point in the history
* Add VS Code workspace to list

* Reuse abm_plot function

* Add agent inspector

* Add option for different colorschemes

* Reuse abm_plot function

* Encapsulate plots and button behavior in own func

* Rename figure -> fig

* Fix typo

* Bump minor version number

* Fix typo

* Fix typo

* Condense function definition

* Dispatch on abmplot

* Include abmplot.jl

* Add basic ABMPlot recipe

* Add agent inspection on mouse hover for 2D

* Add TODOs for 3D and poly

* Propagate kwargs

* Actually enable colorscheme kwarg

* Rename plots and behaviour function

Also initialise the agent and model dataframes outside of the
plots & behaviour function and pass them as kwarg for modification.

* Add colorscheme kwarg default

* Rename df_agent -> adf and df_model -> mdf

* Add kwarg defaults; Pass to init_stepper function

* Remove kwarg defaults

Those defaults are now defined before calling
abm_init_stepper_and_plot! itself (i.e. in the `abm_plot` function),
hopefully allowing for higher reusability.

* Add a few more TODOs

* Move standalone defaults out of kwargs...

Only those kwargs with defaults dependent on other kwargs should
be handled via the get function.
All those keyword arguments that have standalone defaults can be
handled as usual.

* Deal with ContinuousSpace positions for tooltip

* Enable axiskwargs in abm_plot

* Rework abm_video to reuse abm_plot

* Round agent pos for tooltip

* Add helper typenames

* Rename pos to avoid confusion

* Add 3D plot support

* Disable inspection of static preplot

* Add failsafe for nonexistent static plot

* Actually do a typecheck because I'm a bit tired

* Remove obsolete TODOs

* Integrate polygon plots in ABMPlot recipe system

* Poly plots now got both kinds: country and western

* Disable inspector for poly plots

* Round most floats in tooltip

* Export agent2string and add example to docstring

* Escape some things. (Formatting docs is hard.)

* Stop returning inspector

* Separate ini functions for stepper and plots

* Update long list of kwargs

* Update some docstrings

* Add return value

* Add section to docs

Co-authored-by: George Datseris <datseris.george@gmail.com>
  • Loading branch information
fbanning and Datseris committed Nov 7, 2021
1 parent 9026494 commit 25dac30
Show file tree
Hide file tree
Showing 8 changed files with 395 additions and 135 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ update*
Manifest.toml
*.scss
*.css
*.code-workspace
3 changes: 2 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name = "InteractiveDynamics"
uuid = "ec714cd0-5f51-11eb-0b6e-452e7367ff84"
repo = "https://github.com/JuliaDynamics/InteractiveDynamics.jl.git"
version = "0.17.3"
version = "0.18.0"


[deps]
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Expand Down
14 changes: 14 additions & 0 deletions docs/src/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,17 @@ abm_play
abm_video
abm_data_exploration
```

## Agent inspection

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 `id`, `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 [`DataInspector`](https://makie.juliaplots.org/v0.15.1/documentation/inspector/).

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 `A<:AbstractAgent`.

```@docs
agent2string
```
1 change: 1 addition & 0 deletions src/InteractiveDynamics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function __init__()
include("billiards/interactive_billiard.jl")
end
@require Agents = "46ada45e-f475-11e8-01d0-f70cc89e6671" begin
include("agents/abmplot.jl")
include("agents/stepping.jl")
include("agents/plots_videos.jl")
include("agents/interactive_parameters.jl")
Expand Down
210 changes: 210 additions & 0 deletions src/agents/abmplot.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
export agent2string

##########################################################################################
# ABMPlot recipe
##########################################################################################

"Define ABMPlot plotting function with some attribute defaults."
@recipe(ABMPlot, agent_pos, model) do scene
Theme(
# insert InteractiveDynamics theme here?
)
Attributes(
ac = JULIADYNAMICS_COLORS[1],
as = 10,
am = :circle,
scatterkwargs = NamedTuple(),
)
end

# 2D space
function Makie.plot!(abmplot::ABMPlot{<:Tuple{Vector{Point2f0}, <:Agents.ABM}})
scatter!(abmplot, abmplot[:agent_pos];
color=abmplot[:ac], marker=abmplot[:am], markersize=abmplot[:as],
abmplot[:scatterkwargs]...
)

return abmplot
end

# 3D space
function Makie.plot!(abmplot::ABMPlot{<:Tuple{Vector{Point3f0}, <:Agents.ABM}})
abmplot.am[] == :circle && (abmplot.am = Sphere(Point3f0(0), 1))

meshscatter!(abmplot, abmplot[:agent_pos];
color=abmplot[:ac], marker=abmplot[:am], markersize=abmplot[:as],
abmplot[:scatterkwargs]...
)

return abmplot
end

# 2D polygons
function Makie.plot!(abmplot::ABMPlot{<:Tuple{Vector{<:Polygon{2}}, <:Agents.ABM}})
poly!(abmplot, abmplot[:agent_pos];
color=abmplot[:ac],
abmplot[:scatterkwargs]...
)

return abmplot
end

##########################################################################################
# Agent inspection on mouse hover
##########################################################################################

# 2D space
function Makie.show_data(inspector::DataInspector,
plot::ABMPlot{<:Tuple{Vector{Point2f0}, <:Agents.ABM}},
idx, ::Scatter)
a = inspector.plot.attributes
scene = Makie.parent_scene(plot)

proj_pos = Makie.shift_project(scene, plot, to_ndim(Point3f0, plot[1][][idx], 0))
Makie.update_tooltip_alignment!(inspector, proj_pos)
as = plot.as[]

cursor_pos = (plot[1][][idx].data[1], plot[1][][idx].data[2])
s = typeof(plot.model[].space)
if s <: Agents.ContinuousSpace
cursor_pos = Float64.(cursor_pos)
elseif s <: Agents.GridSpace
cursor_pos = Int.(cursor_pos)
end
a._display_text[] = agent2string(plot.model[], cursor_pos)
a._bbox2D[] = FRect2D(proj_pos .- 0.5 .* as .- Vec2f0(5), Vec2f0(as) .+ Vec2f0(10))
a._px_bbox_visible[] = true
a._bbox_visible[] = false
a._visible[] = true

return true
end

# 3D space
function Makie.show_data(inspector::DataInspector,
plot::ABMPlot{<:Tuple{Vector{Point3f0}, <:Agents.ABM}},
idx, ::MeshScatter)
a = inspector.plot.attributes
scene = Makie.parent_scene(plot)

proj_pos = Makie.shift_project(scene, plot, to_ndim(Point3f0, plot[1][][idx], 0))
Makie.update_tooltip_alignment!(inspector, proj_pos)
as = plot.as[]

cursor_pos = (plot[1][][idx].data[1], plot[1][][idx].data[2], plot[1][][idx].data[3])
s = typeof(plot.model[].space)
if s <: Agents.ContinuousSpace
cursor_pos = Float64.(cursor_pos)
elseif s <: Agents.GridSpace
cursor_pos = Int.(cursor_pos)
end
a._display_text[] = agent2string(plot.model[], cursor_pos)
a._bbox2D[] = FRect2D(proj_pos .- 0.5 .* as .- Vec2f0(5), Vec2f0(as) .+ Vec2f0(10))
a._px_bbox_visible[] = true
a._bbox_visible[] = false
a._visible[] = true

return true
end

# 2D polygons
# TODO: Fix this tooltip
function Makie.show_data(inspector::DataInspector,
plot::ABMPlot{<:Tuple{Vector{<:Polygon{2}}, <:Agents.ABM}},
idx, ::Makie.Poly)
a = inspector.plot.attributes
scene = Makie.parent_scene(plot)

proj_pos = Makie.shift_project(scene, plot, to_ndim(Point3f0, plot[1][][idx], 0))
Makie.update_tooltip_alignment!(inspector, proj_pos)
as = plot.as[]

cursor_pos = (plot[1][][idx].data[1], plot[1][][idx].data[2])
s = typeof(plot.model[].space)
if s <: Agents.ContinuousSpace
cursor_pos = Float64.(cursor_pos)
elseif s <: Agents.GridSpace
cursor_pos = Int.(cursor_pos)
end
a._display_text[] = agent2string(plot.model[], cursor_pos)
a._bbox2D[] = FRect2D(proj_pos .- 0.5 .* as .- Vec2f0(5), Vec2f0(as) .+ Vec2f0(10))
a._px_bbox_visible[] = true
a._bbox_visible[] = false
a._visible[] = true

return true
end

DiscretePos = Union{NTuple{2, Int}, NTuple{3, Int}}
ContinuousPos = Union{NTuple{2, Float64}, NTuple{3, Float64}}

function agent2string(model::Agents.ABM, cursor_pos::DiscretePos)
ids = Agents.ids_in_position(cursor_pos, model)
s = ""

for id in ids
s *= agent2string(model[id]) * "\n"
end

return s
end

function agent2string(model::Agents.ABM, cursor_pos::ContinuousPos)
ids = Agents.nearby_ids(cursor_pos, model, 0.01)
s = ""

for id in ids
s *= agent2string(model[id]) * "\n"
end

return s
end

"""
agent2string(agent::A)
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 `agent2string`.
Example:
```julia
import InteractiveDynamics.agent2string
function agent2string(agent::SpecialAgent)
\"\"\"
✨ SpecialAgent ✨
ID = \$(agent.id)
Main weapon = \$(agent.charisma)
Side weapon = \$(agent.pistol)
\"\"\"
end
```
"""
function agent2string(agent::A) where {A<:Agents.AbstractAgent}
agentstring = "$(nameof(A))\n"

agentstring *= "id: $(getproperty(agent, :id))\n"

agent_pos = getproperty(agent, :pos)
typeof(agent_pos) <: ContinuousPos && (agent_pos = round.(agent_pos, digits=2))
agentstring *= "pos: $(agent_pos)\n"

for field in fieldnames(A)[3:end]
val = getproperty(agent, field)
V = typeof(val)
if V <: AbstractFloat
val = round(val, digits=2)
elseif V <: AbstractArray{<:AbstractFloat}
val = round.(val, digits=2)
elseif V <: Tuple && V <: NTuple{length(val), <:AbstractFloat}
val = round.(val, digits=2)
end
agentstring *= "$(field): $val\n"
end

return agentstring
end
Loading

0 comments on commit 25dac30

Please sign in to comment.