Skip to content
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

Support dynamic components #2446

Merged
merged 4 commits into from
Oct 23, 2023
Merged
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
2 changes: 1 addition & 1 deletion glue/core/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -1543,7 +1543,7 @@ def update_components(self, mapping):

# alert hub of the change
if self.hub is not None:
msg = NumericalDataChangedMessage(self)
msg = NumericalDataChangedMessage(self, components_changed=list(mapping.keys()))
self.hub.broadcast(msg)

for subset in self.subsets:
Expand Down
6 changes: 6 additions & 0 deletions glue/core/layer_artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,12 @@ def _check_subset_state_changed(self):
self._changed = True
self._state = state

def _on_components_changed(self, components_changed):
"""
React to a change to one or more of the components in this layer.
"""
pass

def __str__(self):
return "%s for %s" % (self.__class__.__name__, self.layer.label)

Expand Down
4 changes: 3 additions & 1 deletion glue/core/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,9 @@ def __init__(self, sender, attribute, tag=None):


class NumericalDataChangedMessage(DataMessage):
pass
def __init__(self, sender, components_changed=None, tag=None):
super(NumericalDataChangedMessage, self).__init__(sender, tag=tag)
self.components_changed = components_changed


class DataCollectionMessage(Message):
Expand Down
45 changes: 45 additions & 0 deletions glue/core/tests/test_components_changed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""
Test that data.update_components() sends a NumericalDataChangedMessage
that conveys which components have been changed.
"""
from glue.core.data import Data
from glue.core.hub import HubListener
from glue.core.data_collection import DataCollection
from glue.core.message import NumericalDataChangedMessage

import numpy as np
from numpy.testing import assert_array_equal


def test_message_carries_components():

test_data = Data(x=np.array([1, 2, 3, 4, 5]), y=np.array([1, 2, 3, 4, 5]), label='test_data')
data_collection = DataCollection([test_data])

class CustomListener(HubListener):

def __init__(self, hub):
self.received = 0
self.components_changed = None
hub.subscribe(self, NumericalDataChangedMessage,
handler=self.receive_message)

def receive_message(self, message):
self.received += 1
try:
self.components_changed = message.components_changed
except AttributeError:
self.components_changed = None

listener = CustomListener(data_collection.hub)
assert listener.received == 0
assert listener.components_changed is None

cid_to_change = test_data.id['x']
new_data = [5, 2, 6, 7, 10]
test_data.update_components({cid_to_change: new_data})

assert listener.received == 1
assert cid_to_change in listener.components_changed

assert_array_equal(test_data['x'], new_data)
58 changes: 58 additions & 0 deletions glue/viewers/common/tests/test_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from glue.core import Data
from glue.viewers.common.viewer import Viewer
from glue.viewers.common.layer_artist import LayerArtist
from glue.core.message import NumericalDataChangedMessage


def test_custom_layer_artist_maker():
Expand Down Expand Up @@ -45,3 +46,60 @@ def custom_maker(viewer, data):
viewer.add_data(data2)
assert len(viewer.layers) == 2
assert type(viewer.layers[1]) is CustomLayerArtist


def test_viewer_update_data():
"""
Test that we can have a LayerArtist that can respond
to a NumericalDataChangedMessage.
"""

class CustomUpdateLayerArtist(LayerArtist):

def _on_components_changed(self, components_changed):
self.called_component_limits = True
self.num_components_changed = len(components_changed)

class CustomApplication(Application):
def add_widget(self, *args, **kwargs):
pass

@layer_artist_maker('custom_maker_for_update')
def custom_maker_for_update(viewer, data):
if hasattr(data, 'custom_for_update'):
return CustomUpdateLayerArtist(viewer.state, layer=data)
if hasattr(data.data, 'custom_for_update'):
return CustomUpdateLayerArtist(viewer.state, layer=data)

app = CustomApplication()

data1 = Data(x=[1, 2, 3], label='test1')
data1.custom_for_update = True

app.data_collection.append(data1)

viewer = app.new_data_viewer(Viewer)

assert len(viewer.layers) == 0

# NOTE: Check exact type, not using isinstance
viewer.add_data(data1)
assert len(viewer.layers) == 1
assert type(viewer.layers[0]) is CustomUpdateLayerArtist

msg = NumericalDataChangedMessage(data1, components_changed=[data1.id['x']])

viewer._update_data(msg)
assert viewer.layers[0].called_component_limits
assert viewer.layers[0].num_components_changed == 1

subset = data1.new_subset()
subset.subset_state = data1.id['x'] > 2

assert len(viewer.layers) == 2
assert type(viewer.layers[1]) is CustomUpdateLayerArtist

msg = NumericalDataChangedMessage(data1, components_changed=[data1.id['x']])
viewer._update_data(msg)
assert viewer.layers[1].called_component_limits
assert viewer.layers[1].num_components_changed == 1
11 changes: 11 additions & 0 deletions glue/viewers/common/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,20 @@ def _update_data(self, message):
if isinstance(layer_artist.layer, Subset):
if layer_artist.layer.data is message.data:
layer_artist.update()
try:
components_changed = message.components_changed
layer_artist._on_components_changed(components_changed)
except AttributeError:
pass

else:
if layer_artist.layer is message.data:
layer_artist.update()
try:
components_changed = message.components_changed
layer_artist._on_components_changed(components_changed)
except AttributeError:
pass

def _update_subset(self, message):
if message.attribute == 'style':
Expand Down