Sorry, something went wrong.
|
@almarklein if you have time it would be great to get your thoughts, I've summarized what this does here since it's a massive diff: The majority of this PR implements this: #389 (comment) I decided to go for explict properties instead of descriptors because it shows up better for typing and docstring popups. API comparisionBasic features (no buffer)# before this PR, setting and getting a feature value
>>> line_graphic.thickness = 3.5
>>> line_graphic.thickness()
3.5
# this PR, getter & setter are symmetric when possible
>>> line_graphic.thickness = 3.5
>>> line_graphic.thickness
3.5
Sliceable features (manages a buffer)# setting and getting the "entire value"
>>> line_graphic.colors = "r"
>>> line_graphic.colors
<__repr__ prints array>
# this PR, similar but value can be directly access if desired
>>> line_graphic.colors = "r"
>>> line_graphic.colors
<__repr__ prints array>
>> line_graphic.colors.value
<returns the actual array>
Sharing buffers 😄 ! # let's say you have a line with some vertex colors
>>> line1.colors
<returns array for red>
>>> line2 = subplot.add_line(data, colors=line1.colors) # share buffer :D !
# modify colors
>>> line2.colors[50:] = "b"
# change of colors is reflected in BOTH graphics :D
The implementation is very simple, for example in PositionsGraphic.__init__: class PositionsGraphic(Graphic):
def __init__(self, data, colors, ...):
if isinstance(data, VertexPositions):
self._data = data
else:
self._data = VertexPositions(data, isolated_buffer=isolated_buffer)
As we can see, it actually shares the BufferManager (i.e. feature) instance. This is simpler and makes it so changing the buffer causes events to be emitted even if it's from another graphic. In the above example, if an event handler is added to line1, i.e. line1.add_event_handler(<callable>, "colors"), then changing the colors of line2 would also trigger the event which I assume is what you would want since line1 colors have also changed. Events# before this PR
line_graphic.thickness.add_event_handler(<callable>)
# this PR
line_graphic.add_event_handler(<callable>, "thickness")
# can also use decorators :D
@line_graphic.add_event_handler("thickness")
def thickness_changed_handler(ev):
pass
# exactly the same for features that subclass BufferManager
@line_graphic.add_event_handler("colors", "data")
def line_colors_or_data_changed(ev):
if ev.type == "colors":
print(
f"colors changed for graphic: <{ev.graphic}>
F"with slice: {ev.info['key']} and new values: {ev.info['value']}"
)
if ev.type == "data":
print(
f"data changed for graphic: <{ev.graphic}>
F"with slice: {ev.info['key']} and new values: {ev.info['value']}"
)
# rendering engine events added in exactly the same way
@image_graphic.add_event_handler("click")
def image_clicked(ev):
ev.graphic # the graphic that caused the event
The event adding and handling is managed here, mostly copy-pasted from pygfx. fastplotlib/fastplotlib/graphics/_base.py Lines 209 to 332 in 47cecfa We handle the events in a wrapper method which appends the Graphic instead that caused the event, and also adds data and world (x, y) for pointer events. Before this PR, the event info for "graphic features" was a mess that we probably don't need to dig into 😆 . Now every FeatureEvent inherits from pygfx.Event and has the following attributes:
The "info" dict for all "simple" features has one key: "value" - the user passed new value. Example if line_graphic.name is changed, then the info dict will be: {"value": "new_name_str"}. The info dict will have an additional "key" entry if it's from a buffer manager feature. BufferManager example info dicts:colors (vertex colors, line or scatter)
data (points data, line or scatter)
SelectorsAdding event handlers to a selector is identical to how they're added for "regular" graphics: # one of the selectors will change the line colors when it moves
@selector.add_event_handler("selection")
def set_color_at_index(ev):
# changes the color at the index where the slider is
ix = ev.get_selected_index()
g = ev.graphic.parent
g.colors[ix] = "green"
Selector feature event structure LinearSelector "selection" additional event attributes:
info dict:
LinearRegioonSelector additional event attributes:
info dict:
selector is the LinearSelector or LinearRegionSelector that the feature manages, and it has the get_selected_index, or get_selected_indices and get_selected_data methods for LinearRegionSelector GraphicsSome major refactor to graphics not mentioned above:
|
Sorry, something went wrong.
|
Bug when adding an existing graphic to another plot import fastplotlib as fpl
import numpy as np
data = np.random.rand(512, 512)
fig = fpl.Figure()
fig[0,0].add_image(data=data)
fig2 = fpl.Figure()
fig2[0,0].add_graphic(fig[0,0].graphics[0])
yields TypeError Traceback (most recent call last)
Cell In[3], line 3
1 fig2 = fpl.Figure()
----> 3 fig2[0,0].add_graphic(fig[0,0].graphics[0])
File [~/repos/fastplotlib/fastplotlib/layouts/_plot_area.py:468](http://localhost:8888/home/caitlin/repos/fastplotlib/fastplotlib/layouts/_plot_area.py#line=467), in PlotArea.add_graphic(self, graphic, center)
465 self.scene.add(graphic.world_object)
466 return
--> 468 self._add_or_insert_graphic(graphic=graphic, center=center, action="add")
470 if self.camera.fov == 0:
471 # for orthographic positions stack objects along the z-axis
472 # for perspective projections we assume the user wants full 3D control
473 graphic.offset = (*graphic.offset[:-1], len(self))
File [~/repos/fastplotlib/fastplotlib/layouts/_plot_area.py:558](http://localhost:8888/home/caitlin/repos/fastplotlib/fastplotlib/layouts/_plot_area.py#line=557), in PlotArea._add_or_insert_graphic(self, graphic, center, action, index)
555 REFERENCES.add(graphic)
557 # now that it's in the dict, just use the weakref
--> 558 graphic = weakref.proxy(graphic)
560 # add world object to scene
561 self.scene.add(graphic.world_object)
TypeError: cannot create weak reference to 'weakref.ProxyType' object
also, even just trying to access a graphic fig[0,0].graphics[0]
----> 1 fig[0,0].graphics[0]
File [~/repos/fastplotlib/fastplotlib/layouts/_plot_area.py:280](http://localhost:8888/home/caitlin/repos/fastplotlib/fastplotlib/layouts/_plot_area.py#line=279), in PlotArea.graphics(self)
277 @property
278 def graphics(self) -> tuple[Graphic, ...]:
279 """Graphics in the plot area. Always returns a proxy to the Graphic instances."""
--> 280 return REFERENCES.get_proxies(self._graphics)
File [~/repos/fastplotlib/fastplotlib/layouts/_plot_area.py:62](http://localhost:8888/home/caitlin/repos/fastplotlib/fastplotlib/layouts/_plot_area.py#line=61), in References.get_proxies(self, refs)
60 for key in refs:
61 if key in self._graphics.keys():
---> 62 proxies.append(weakref.proxy(self._graphics[key]))
64 elif key in self._selectors.keys():
65 proxies.append(weakref.proxy(self._selectors[key]))
TypeError: cannot create weak reference to 'weakref.ProxyType' object
I think we are passing around the weakrefs everywhere and then we end up trying to weakref a weakref |
Sorry, something went wrong.
There was a problem hiding this comment.
@almarklein if you have time it would be great to get your thoughts, I've summarized what this does here since it's a massive diff
The description you posted sounds very good!
Sharing buffers
Hooray! Feels nice and simple.
Adding event handlers to a selector is identical to how they're added for "regular" graphics:
Nice, less stuff to learn for users.
I also glanced over the diff. Looks nice from a birds-eye view. Made a few minor comments/suggestions.
Sorry, something went wrong.
|
failing because camera stuff was recently merged in pygfx, I might as well update this PR with it 🫠 |
Sorry, something went wrong.
|
failing because camera stuff was recently merged in pygfx You mean pygfx/pygfx#778? I was under the impression that that change was backwards compatible. |
Sorry, something went wrong.
|
It's specific to how we allow changing the controller of an existing plot area: https://github.com/fastplotlib/fastplotlib/actions/runs/9492712816/job/26160279159?pr=511#step:9:895 |
Sorry, something went wrong.
There was a problem hiding this comment.
YAYAY!!!
Sorry, something went wrong.
|
Everything passing on our ends locally, merging. Need to wait for pygfx to fix pygfx/pygfx#790 and for things to catch up with numpy v2 before all our CI works again. |
Sorry, something went wrong.
WIP
closes #389, closes #489, closes #499, closes #456, closes #369, closes #322, closes #316, closes #309, closes #283, closes #174, closes #147, closes #126
closes #507, closes #449, closes #123, closes #214
Remaining TODOs
Summary
Graphic Features
Graphic Features that are sliceable, subclasses BufferManager
Selector tools
API comparision
Basic features (no buffer)
Sliceable features (manages a buffer)
Sharing buffers 😄 !
The implementation is very simple, for example in PositionsGraphic.__init__:
As we can see, it actually shares the BufferManager (i.e. feature) instance. This is simpler and makes it so changing the buffer causes events to be emitted even if it's from another graphic. In the above example, if an event handler is added to line1, i.e. line1.add_event_handler(<callable>, "colors"), then changing the colors of line2 would also trigger the event which I assume is what you would want since line1 colors have also changed.
Events
The event adding and handling is managed here:
fastplotlib/fastplotlib/graphics/_base.py
Lines 173 to 278 in 1d6adc6
Before this PR, the event info was a mess that we probably don't need to dig into 😆 . Now every FeatureEvent inherits from pygfx.Event and has the following attributes:
The "info" dict for all "simple" features has one key, "value", which is the user passed new value. Example if line_graphic.name is changed, then the info dict will be: {"value": "new_name_str"}
BufferManager example info dicts:
colors (vertex colors, line or scatter)
data (points data, line or scatter)
Selectors
Adding event handlers to a selector is identical to how they're added for "regular" graphics:
Selector feature event structure
LinearSelector "selection"
additional event attributes:
info dict:
LinearRegioonSelector
additional event attributes:
info dict:
This is how it's implemented:
fastplotlib/fastplotlib/graphics/_features/_selection_features.py
Lines 57 to 76 in 1d6adc6
selector is the LinearSelector (in this case) or LinearRegionSelector that the feature manages, and it has the get_selected_index, or get_selected_indices and get_selected_data methods for LinearRegionSelector
Graphics
Some major refactor to graphics not mentioned above: