VoronoiMesh as a TogglerBar
ClearAll[togglerMesh]
togglerMesh = DynamicModule[{ m = #, ids = {},
nF = Nearest[PropertyValue[{#, 2}, MeshCellCentroid] -> "Index"]},
Dynamic@EventHandler[HighlightMesh[m, Thread[{2, Flatten@ids}]],
"MouseClicked" :> If[MemberQ[ids, #], ids = DeleteCases[ids, #],
AppendTo[ids, #]] &[If[MousePosition["Graphics"] === None, {},
First@nF[MousePosition["Graphics"]]]]]] &;
Examples:
SeedRandom[1]
pts = RandomReal[{-1, 1}, {20, 2}];
vm = VoronoiMesh[pts, {{-1, 1}, {-1, 1}}];
togglerMesh[vm]
dm = DelaunayMesh[pts, {{-1, 1}, {-1, 1}}];
togglerMesh[dm]
Using a hexagonal mesh (from this answer):
SeedRandom[1]
pts = Flatten[Table[{3/2 i + RandomReal[.5],
Sqrt[3] j + Mod[i, 2] Sqrt[3]/2 + RandomReal[.5]}, {i, 7}, {j, 7}], 1];
hexmesh = DiscretizeGraphics @ Graphics @
Select[Length[#[[1]]] == 6 &] @ MeshPrimitives[VoronoiMesh[pts], {2, "Interior"}];
togglerMesh[hexmesh]
Here are implementations for a MeshTogglerBar
and MeshSetterBar
based on my answer here (code below). Both implementations use Mouseover
and EventHandler
to handle detection of the polygon below the cursor for you. Compared to the NearestFunction
approach, this is far more performant (since it is done by the front-end), it also works nicely for other types of meshes, where the cell below the cursor is not necessarily the one with the closest center.
TogglerBar
SetterBar
Code
MeshTogglerBar[mesh_] := iMeshTogglerBar[#, mesh] &
Dynamic[MeshTogglerBar[mesh_]] ^:=
Dynamic[iMeshTogglerBar[#, mesh] &]
MeshTogglerBar[Dynamic@var_, mesh_] :=
iMeshTogglerBar[Dynamic@var, mesh]
iMeshTogglerBar[Dynamic@var_, mesh_] := Module[
{prims = MeshPrimitives[mesh, 2]},
With[
{
active =
Append[dragAction]@Table[Unique["active"], Length@prims],
n = Length@prims
},
DynamicModule[
active,
Graphics[
{
FaceForm@White, EdgeForm@Blue,
MapIndexed[
With[
{v = active[[#2[[1]]]]},
EventHandler[
Style[
Annotation[#, ""],
TagBoxOptions -> {
BaseStyle -> FEPrivate`Which[
FEPrivate`SameQ[v, True],
{Lighter@Blue, EdgeForm@{Thick, Blue}},
FrontEnd`CurrentValue@"MouseOver",
LightBlue,
True,
{}
]
}
],
{
"MouseEntered" :> FEPrivate`If[
FEPrivate`And[
FrontEnd`CurrentValue[{"MouseButtonTest", 1}],
FEPrivate`UnsameQ[v, dragAction]
],
FEPrivate`Set[v, dragAction];
var[[#2[[1]]]] = dragAction
],
{"MouseDown", 1} :> (
FEPrivate`Set[dragAction, FEPrivate`UnsameQ[v, True]];
FEPrivate`Set[v, dragAction];
var[[#2[[1]]]] = dragAction
)
}
]
] &,
prims
]
},
ImageSize -> Medium
],
Initialization :> (
If[ListQ@var,
var = TrueQ /@ PadLeft[var, n, False],
var = ConstantArray[False, n]
];
MapThread[Set, {Most@active, var}]
)
]
]
]
MeshSetterBar[mesh_] := iMeshSetterBar[#, mesh] &
Dynamic[MeshSetterBar[mesh_]] ^:= Dynamic[iMeshSetterBar[#, mesh] &]
MeshSetterBar[Dynamic@var_, mesh_] := iMeshSetterBar[Dynamic@var, mesh]
iMeshSetterBar[Dynamic@var_, mesh_] :=
DynamicModule[
{active},
Graphics[
{
FaceForm@White,
EdgeForm@Blue,
MapIndexed[
EventHandler[
Style[
Annotation[#, ""],
TagBoxOptions -> {
BaseStyle -> FEPrivate`Which[
FEPrivate`SameQ[active, #2[[1]]],
{Lighter@Blue, EdgeForm@{Thick, Blue}},
FrontEnd`CurrentValue@"MouseOver",
LightBlue,
True,
{}
]
}
],
{"MouseClicked" :> (
FEPrivate`Set[active, #2[[1]]]; var = #2[[1]]
)
}
] &,
MeshPrimitives[mesh, 2]
]
},
ImageSize -> Medium
],
Initialization :> (active =var)
]
SeedRandom[1]
mesh = VoronoiMesh@RandomReal[{0, 1}, {10, 2}]
Dynamic@x
MeshSetterBar[Dynamic@x, mesh]
Dynamic@x
MeshTogglerBar[Dynamic@x, mesh]
Notes
Some notes on the implementation (you can find some more in my answer linked above):
- Since everything is handled by the front-end, these controls will have excellent performance
- For the
MeshTogglerBar
, we have to generate a list of state variables (one per cell). This is because the front-end cannot manipulate lists, so each cell needs a separate variable - The default values of the state variables are set in the
Initialization
property of theDynamicModule
to ensure that the values are not prematurely inserted anywhere. - The dynamic styling is done via
TagBoxOptions -> {BaseStyle -> {...}}
. This is done since we need to set the styles via an option for the front-end-only solution to work. TheAnnotation[...]
/TagBoxOptions
trick is to ensure that any type of primitive is styled, not onlyPolygon
s. - The controlled variables are kept separate from the
DynamicModule
variables used to store the state of the control. This ensures that the front-end ↔ kernel communication is kept to a minimum (i.e. only when a click has happened is the kernel variable updated). - For the
MeshTogglerBar
, we trigger on both"MouseEntered"
and"MouseDown"
to enable dragging over many elements to toggle them. The state of the first element is stored indragAction
, to ensure that dragging sets all elements to the same state instead of toggling them back and forth The
iMeshTogglerBar
/iMeshSetterBar
functions are there so the control can be easily used insideManipulate
:Manipulate[ x, {{x, 3}, MeshSetterBar[mesh]} ]
Similarly, the
Dynamic[MeshSetterBar[_]]
/Dynamic[MeshTogglerBar[_]]
type definitions are to ensure that the controls work inside ofManipulate
when the controls depend on other variables:Manipulate[x, {n, 2, 10, 1}, {{x, 3, ""}, MeshSetterBar[VoronoiMesh@RandomReal[{0, 1}, {n, 2}]]} ]
The additional definition is necessary, since
Manipulate
wraps control specifications inDynamic
if any other manipulate variables occur in the specifications. This preventsManipulator
from seeing theFunction
expression, since it is not evaluated. The additional upvalue forces evaluation into something with an explicitFunction
in those cases.
SeedRandom[421]
points = RandomReal[{-1, 1}, {10, 2}];
mesh = VoronoiMesh[points, {{-1, 1}, {-1, 1}}];
nf = Nearest[points -> Automatic];
primitives = MeshPrimitives[mesh, {2, All}];
Dynamic[
ClickPane[
HighlightMesh[mesh, {2, #}] & @@
FirstPosition[
primitives,
SelectFirst[primitives, RegionMember[#, Extract[points, selected]] &]
],
(selected = nf[#]) &
],
Initialization :> (selected = {1})
]
@MichaelE2's answer to Clickable Bounded Diagram provided key inspiration; @PlatoManiac's code in their answer to How to find adjacent polygons of a specific polygon in a VoronoiMesh helped with the selection of the cell within which the mouse click is found