Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Interactivity #84

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
430 changes: 430 additions & 0 deletions 430 examples/event_handler.ipynb

Large diffs are not rendered by default.

302 changes: 302 additions & 0 deletions 302 examples/linecollection_event.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion 2 examples/lineplot.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.5"
"version": "3.9.2"
}
},
"nbformat": 4,
Expand Down
251 changes: 251 additions & 0 deletions 251 examples/single_contour_event.ipynb

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions 4 fastplotlib/graphics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
"LineGraphic",
"HistogramGraphic",
"HeatmapGraphic",
"LineCollection",
"TextGraphic"
"TextGraphic",
"LineCollection"
]
69 changes: 61 additions & 8 deletions 69 fastplotlib/graphics/_base.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from typing import *
import numpy as np

import pygfx

from ..utils import get_colors
from .features import GraphicFeature, DataFeature, ColorFeature, PresentFeature

from abc import ABC, abstractmethod
from dataclasses import dataclass

class Graphic:
def __init__(
Expand Down Expand Up @@ -46,6 +49,7 @@ def __init__(
self.colors = None

self.name = name
self.registered_callbacks = dict()

if n_colors is None:
n_colors = self.data.feature_data.shape[0]
Expand All @@ -60,23 +64,20 @@ def __init__(
# useful for bbox calculations to ignore these Graphics
self.present = PresentFeature(parent=self)

valid_features = ["visible"]
#valid_features = ["visible"]
self._feature_events = list()
for attr_name in self.__dict__.keys():
attr = getattr(self, attr_name)
if isinstance(attr, GraphicFeature):
valid_features.append(attr_name)
self._feature_events.append(attr_name)

self._valid_features = tuple(valid_features)
self._feature_events = tuple(self._feature_events)
self._pygfx_events = ("click",)

@property
def world_object(self) -> pygfx.WorldObject:
return self._world_object

@property
def interact_features(self) -> Tuple[str]:
"""The features for this ``Graphic`` that support interaction."""
return self._valid_features

@property
def visible(self) -> bool:
return self.world_object.visible
Expand Down Expand Up @@ -104,3 +105,55 @@ def __repr__(self):
return f"'{self.name}' fastplotlib.{self.__class__.__name__} @ {hex(id(self))}"
else:
return f"fastplotlib.{self.__class__.__name__} @ {hex(id(self))}"

class Interaction(ABC):
@abstractmethod
def _set_feature(self, feature: str, new_data: Any, indices: Any):
pass

@abstractmethod
def _reset_feature(self, feature: str):
pass

def link(self, event_type: str, target: Any, feature: str, new_data: Any, indices_mapper: callable = None):
if event_type in self._pygfx_events:
self.world_object.add_event_handler(self.event_handler, event_type)
elif event_type in self._feature_events:
feature = getattr(self, event_type)
feature.add_event_handler(self.event_handler, event_type)
else:
raise ValueError("event not possible")

if event_type in self.registered_callbacks.keys():
self.registered_callbacks[event_type].append(
CallbackData(target=target, feature=feature, new_data=new_data, indices_mapper=indices_mapper))
else:
self.registered_callbacks[event_type] = list()
self.registered_callbacks[event_type].append(
CallbackData(target=target, feature=feature, new_data=new_data, indices_mapper=indices_mapper))

def event_handler(self, event):
event_info = event.pick_info
#click_info = np.array(event.pick_info["index"])
if event.type in self.registered_callbacks.keys():
for target_info in self.registered_callbacks[event.type]:
if target_info.indices_mapper is not None:
indices = target_info.indices_mapper(source=self, target=target_info.target, indices=click_info)
else:
indices = None
# set feature of target at indice using new data
target_info.target._set_feature(feature=target_info.feature, new_data=target_info.new_data, indices=indices)

@dataclass
class CallbackData:
"""Class for keeping track of the info necessary for interactivity after event occurs."""
target: Any
feature: str
new_data: Any
indices_mapper: callable = None

@dataclass
class PreviouslyModifiedData:
"""Class for keeping track of previously modified data at indices"""
previous_data: Any
previous_indices: Any
8 changes: 1 addition & 7 deletions 8 fastplotlib/graphics/heatmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,18 @@ def __init__(
):
"""
Create a Heatmap Graphic

Parameters
----------
data: array-like, must be 2-dimensional
| array-like, usually numpy.ndarray, must support ``memoryview()``
| Tensorflow Tensors also work _I think_, but not thoroughly tested

vmin: int, optional
minimum value for color scaling, calculated from data if not provided

vmax: int, optional
maximum value for color scaling, calculated from data if not provided

cmap: str, optional
colormap to use to display the image data, default is ``"plasma"``

selection_options

args:
additional arguments passed to Graphic
kwargs:
Expand Down Expand Up @@ -140,4 +134,4 @@ def add_highlight(self, event):
self.world_object.add(self.selection_graphic)
self._highlights.append(self.selection_graphic)

return rval
return rval
10 changes: 8 additions & 2 deletions 10 fastplotlib/graphics/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import numpy as np
import pygfx

from ._base import Graphic
from ._base import Graphic, Interaction
from ..utils import quick_min_max, get_cmap_texture


class ImageGraphic(Graphic):
class ImageGraphic(Graphic, Interaction):
def __init__(
self,
data: Any,
Expand Down Expand Up @@ -72,6 +72,12 @@ def __init__(
pygfx.ImageBasicMaterial(clim=(vmin, vmax), map=get_cmap_texture(cmap))
)

def _set_feature(self, feature: str, new_data: Any, indices: Any):
pass

def _reset_feature(self, feature: str, old_data: Any):
pass

@property
def clim(self) -> Tuple[float, float]:
return self.world_object.material.clim
Expand Down
43 changes: 35 additions & 8 deletions 43 fastplotlib/graphics/line.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import numpy as np
import pygfx

from ._base import Graphic
from ._base import Graphic, Interaction, PreviouslyModifiedData


class LineGraphic(Graphic):
class LineGraphic(Graphic, Interaction):
def __init__(
self,
data: Any,
Expand All @@ -18,25 +18,19 @@ def __init__(
):
"""
Create a line Graphic, 2d or 3d

Parameters
----------
data: array-like
Line data to plot, 2D must be of shape [n_points, 2], 3D must be of shape [n_points, 3]

z_position: float, optional
z-axis position for placing the graphic

size: float, optional
thickness of the line

colors: str, array, or iterable
specify colors as a single human readable string, a single RGBA array,
or an iterable of strings or RGBA arrays

cmap: str, optional
apply a colormap to the line instead of assigning colors manually

args
passed to Graphic
kwargs
Expand All @@ -59,3 +53,36 @@ def __init__(
)

self.world_object.position.z = z_position

def _set_feature(self, feature: str, new_data: Any, indices: Any = None):
if not hasattr(self, "_previous_data"):
self._previous_data = {}
elif hasattr(self, "_previous_data"):
self._reset_feature(feature)
if feature in self._feature_events:
feature_instance = getattr(self, feature)
if indices is not None:
previous = feature_instance[indices].copy()
feature_instance[indices] = new_data
else:
previous = feature_instance[:].copy()
feature_instance[:] = new_data
if feature in self._previous_data.keys():
self._previous_data[feature].previous_data = previous
self._previous_data[feature].previous_indices = indices
else:
self._previous_data[feature] = PreviouslyModifiedData(previous_data=previous, previous_indices=indices)
else:
raise ValueError("name arg is not a valid feature")


def _reset_feature(self, feature: str):
if feature not in self._previous_data.keys():
raise ValueError("no previous data registered for this feature")
else:
feature_instance = getattr(self, feature)
if self._previous_data[feature].previous_indices is not None:
feature_instance[self._previous_data[feature].previous_indices] = self._previous_data[feature].previous_data
else:
feature_instance[:] = self._previous_data[feature].previous_data

49 changes: 41 additions & 8 deletions 49 fastplotlib/graphics/linecollection.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import numpy as np
import pygfx
from typing import Union
from .line import LineGraphic
from typing import Union, List

from fastplotlib.graphics.line import LineGraphic
from typing import *
from fastplotlib.graphics._base import Interaction
from abc import ABC, abstractmethod

class LineCollection:
def __init__(self, data: List[np.ndarray],
z_position: Union[List[float], float] = None,
size: Union[float, List[float]] = 2.0,
colors: Union[List[np.ndarray], np.ndarray] = None,
cmap: Union[List[str], str] = None,
*args,
**kwargs):

class LineCollection():
def __init__(self, data: List[np.ndarray], z_position: Union[List[float], float] = None, size: Union[float, List[float]] = 2.0, colors: Union[List[np.ndarray], np.ndarray] = None,
cmap: Union[List[str], str] = None, *args, **kwargs):
self.name = None

if not isinstance(z_position, float) and z_position is not None:
if not len(data) == len(z_position):
Expand All @@ -22,7 +31,8 @@ def __init__(self, data: List[np.ndarray], z_position: Union[List[float], float]
if not len(data) == len(cmap):
raise ValueError("args must be the same length")

self.collection = list()
self.data = list()
self._world_object = pygfx.Group()

for i, d in enumerate(data):
if isinstance(z_position, list):
Expand All @@ -45,10 +55,33 @@ def __init__(self, data: List[np.ndarray], z_position: Union[List[float], float]
else:
_cmap = cmap

self.collection.append(LineGraphic(d, _z, _size, _colors, _cmap))
lg = LineGraphic(d, _z, _size, _colors, _cmap)
self.data.append(lg)
self._world_object.add(lg.world_object)

# TODO: make a base class for Collection graphics and put this as a base method
@property
def world_object(self) -> pygfx.WorldObject:
return self._world_object

def _set_feature(self, feature: str, new_data: Any, indices: Any):
if feature in self.features:
update_func = getattr(self.data[indices], f"update_{feature}")
# if indices is a single indices or list of indices
self.data[indices].update_colors(new_data)
else:
raise ValueError("name arg is not a valid feature")

def _reset_feature(self, feature: str, old_data: Any):
if feature in self.features:
#update_func = getattr(self, f"update_{feature}")
for i, line in enumerate(self.data):
line.update_colors(old_data[i])
else:
raise ValueError("name arg is not a valid feature")

def __getitem__(self, item):
return self.collection[item]
return self.data[item]



Morty Proxy This is a proxified and sanitized view of the page, visit original site.