You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi I was interesed in making a circle selector. I've been playing around with it a little bit but was interested if you had guidance on a couple of points. Primarily when looking at the Rectangle Selector you are using a 3D object rather than a 2D one? Is there a good reason for this?
I think the code that I have (mostly) works although I need to work on the resize and the selection code. Would this be of interest? I can also abstract this to an ellipse if we just make our own custom geometry.
One other question is how is the _move_graphic function called? It would be good to include not only the deltas but the position of the click. Currently, it is hard to know if the user is dragging the circle out/in without the event position. You can kind of see the effect of this in the video where the resize is reversed depending on which part of the circle you click on.
Screen.Recording.2025-03-03.at.8.47.10.AM.mov
classCircleSelectionFeature(GraphicFeature):
""" **additional event attributes:** +----------------------+----------+------------------------------------+ | attribute | type | description | +======================+==========+====================================+ | get_selected_indices | callable | returns indices under the selector | +----------------------+----------+------------------------------------+ | get_selected_data | callable | returns data under the selector | +----------------------+----------+------------------------------------+ **info dict:** +-------------0--+------------+-------------------------------------------+ | dict key | value type | value description | +===============+============+===========================================+ | value | np.ndarray | new [centerx, centery, radius, inner_radius] of selection | +-------------+------------+-------------------------------------------+ | limits | np.ndarray | new [xmin, xmax, ymin, ymax] for the ind | +------------+------------+-------------------------------------------+ | size_limits| np.ndarray | new [xmin, xmax, ymin, ymax] for the ind | +------------+------------+-------------------------------------------+ """def__init__(
self,
value: tuple[float, float, float, float],
limits: tuple[float, float, float, float],
size_limits: tuple[float, float, float, float]=None,
):
super().__init__()
self._limits=limitsself._value=tuple(int(v) forvinvalue)
self._size_limits=size_limits@propertydefvalue(self) ->np.ndarray[float]:
""" (centerx, centery, radius, inner_radius) of the selection, in data space """returnself._valuedefset_value(self, selector, value: Sequence[float]):
""" Set the selection of the rectangle selector. Parameters ---------- selector: RectangleSelector value: (float, float, float, float) new values (centerx, centery, radius, inner_radius) of the selection """ifnotlen(value) ==4:
raiseTypeError(
"Selection must be an array, tuple, list, or sequence in the form of `(xmin, xmax, ymin, ymax)`, ""where `xmin`, `xmax`, `ymin`, `ymax` are numeric values."
)
# convert to arrayvalue=np.asarray(value, dtype=np.float32)
# clip the center values if they are outside of the selection.# the radius should be allowed to move beyond the limitsvalue[0] =value[0].clip(self._limits[0], self._limits[1])
value[1] =value[1].clip(self._limits[2], self._limits[3])
x, y, radius, inner_radius=value# make sure that the radius is greater than 0 and the inner radius is less than the radiusifradius<=0orinner_radius>radius:
returnradial_segments=360cap=generate_cap(radius=radius, height=1, radial_segments=radial_segments, theta_start=0,
theta_length=np.pi*2)
cap_outline=generate_cap(radius=radius, height=1.01, radial_segments=radial_segments, theta_start=0,
theta_length=np.pi*2)
positions, normals, texcoords, indices=capselector.fill.geometry.positions.data[:] =positions+np.array([x, y, 0])
positions, normals, texcoords, indices=cap_outline# shift the circle to the correct positionselector.edges[0].geometry.positions.data[:] =positions[1:] +np.array([x, y, 0])
# change the edge linesself._value=value# send changes to GPUselector.fill.geometry.positions.update_range()
foredgeinselector.edges:
edge.geometry.positions.update_range()
# send eventiflen(self._event_handlers) <1:
returnevent=FeatureEvent("selection", {"value": self.value})
event.get_selected_indices=selector.get_selected_indicesevent.get_selected_data=selector.get_selected_data# calls any eventsself._call_event_handlers(event)
classCircleSelector(BaseSelector):
def__init__(self,
center: Sequence[float],
radius: float,
limits: Sequence[float],
inner_radius: float=0,
parent: Graphic=None,
resizable: bool=True,
fill_color=(0, 0, 0.35),
edge_color=(0.8, 0.6, 0),
vertex_color=(0.7, 0.4, 0),
edge_thickness: float=4,
vertex_thickness: float=8,
arrow_keys_modifier: str="Shift",
size_limits: Sequence[float] =None,
name: str=None,):
ifnotlen(center) ==2:
raiseValueError()
# lots of very close to zero values etc. so round themcenter=tuple(map(round, center))
radius=round(radius)
inner_radius=round(inner_radius)
self._parent=parentself._center=np.asarray(center)
self._resizable=resizableself._limits=np.asarray(limits)
self.size_limits=size_limitscenter=np.asarray(center)
# world object for this will be a group# basic mesh for the fill area of the selector# line for each edge of the selectorgroup=pygfx.Group()
self._fill_color=pygfx.Color(fill_color)
self._edge_color=pygfx.Color(edge_color)
self._vertex_color=pygfx.Color(vertex_color)
ifradius<0:
raiseValueError()
radial_segments=360cap=generate_cap(radius=radius, height=1, radial_segments=radial_segments, theta_start=0,
theta_length=np.pi*2)
cap_outline=generate_cap(radius=radius, height=1.01, radial_segments=radial_segments, theta_start=0,
theta_length=np.pi*2)
positions, normals, texcoords, indices=capcircle_geo=pygfx.Geometry(
indices=indices.reshape((-1, 3)),
positions=positions,
)
self.fill=pygfx.Mesh(circle_geo,
pygfx.MeshBasicMaterial(
color=pygfx.Color(self.fill_color), pick_write=True
),
)
self.fill.world.position= (0, 0, -2)
group.add(self.fill)
positions, normals, texcoords, indices=cap_outlinepositions_outline=positions[1:]
circle_outline_geo=pygfx.Geometry(
indices=indices.reshape((-1, 3))[1:],
positions=positions_outline,
)
outline=pygfx.Line(
geometry=circle_outline_geo,
material=pygfx.LineMaterial(thickness=edge_thickness, color=self.edge_color)
)
self.edges= (outline,)
outline.world.z=-0.5group.add(outline)
selection=np.asarray(tuple(center) + (radius, inner_radius))
self._selection=CircleSelectionFeature(selection, limits=self._limits,
size_limits=size_limits)
# include parent offsetifparentisnotNone:
offset= (parent.offset[0], parent.offset[1], 0)
else:
offset= (0, 0, 0)
BaseSelector.__init__(
self,
edges=self.edges,
fill=(self.fill,),
hover_responsive=(*self.edges,),
arrow_keys_modifier=arrow_keys_modifier,
parent=parent,
name=name,
offset=offset,
)
self._set_world_object(group)
self.selection=selectiondef_move_graphic(self, delta: np.ndarray):
# new selection positionscenterx_new=self.selection[0] +delta[0]
centery_new=self.selection[1] +delta[1]
# move entire selector if source is fillifself._move_info.source==self.fill:
# set thew new boundsself._selection.set_value(self, (centerx_new, centery_new, self.selection[2], self.selection[3]))
self.selection= (centerx_new, centery_new, self.selection[2], self.selection[3])
return# if selector not resizable returnifnotself._resizable:
returnelifself._move_info.source==self.edges[0]:
ifdelta[0] <0ordelta[1] <0:
radius_change=-np.sqrt(delta[0]**2+delta[1]**2)
else:
radius_change=np.sqrt(delta[0]**2+delta[1]**2)
new_radius=self.selection[2] +radius_changeifnew_radius<0:
returnifself.size_limitsisnotNone:
ifnew_radius<self.size_limits[0] ornew_radius>self.size_limits[1]:
returnvalues= (self.selection[0], self.selection[1], self.selection[2]+radius_change, self.selection[3])
self._selection.set_value(self, values)
self.selection=valuesreturn
Hi I was interesed in making a circle selector. I've been playing around with it a little bit but was interested if you had guidance on a couple of points. Primarily when looking at the Rectangle Selector you are using a 3D object rather than a 2D one? Is there a good reason for this?
I think the code that I have (mostly) works although I need to work on the resize and the selection code. Would this be of interest? I can also abstract this to an ellipse if we just make our own custom geometry.
One other question is how is the
_move_graphicfunction called? It would be good to include not only the deltas but the position of the click. Currently, it is hard to know if the user is dragging the circle out/in without the event position. You can kind of see the effect of this in the video where the resize is reversed depending on which part of the circle you click on.Screen.Recording.2025-03-03.at.8.47.10.AM.mov