This repository has been archived by the owner on Jun 21, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Agent inspection on mouse hover (#72)
* 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
Showing
8 changed files
with
395 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,4 @@ update* | |
Manifest.toml | ||
*.scss | ||
*.css | ||
*.code-workspace |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.