From e164546edcef7361864babf2882d4389aa674fc9 Mon Sep 17 00:00:00 2001 From: Jonathan Foster Date: Tue, 19 Sep 2023 12:08:06 -0400 Subject: [PATCH 1/4] Support updating layer artist limits when data changes --- glue/core/layer_artist.py | 6 ++++++ glue/core/message.py | 4 +++- glue/viewers/common/viewer.py | 11 +++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/glue/core/layer_artist.py b/glue/core/layer_artist.py index b7b5b8756..b4eb93ffc 100644 --- a/glue/core/layer_artist.py +++ b/glue/core/layer_artist.py @@ -228,6 +228,12 @@ def _check_subset_state_changed(self): self._changed = True self._state = state + def update_component_limits(self, components_changed): + """ + Update component limits for this layer + """ + pass + def __str__(self): return "%s for %s" % (self.__class__.__name__, self.layer.label) diff --git a/glue/core/message.py b/glue/core/message.py index 94302dd7c..68615ddd1 100644 --- a/glue/core/message.py +++ b/glue/core/message.py @@ -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): diff --git a/glue/viewers/common/viewer.py b/glue/viewers/common/viewer.py index cc2aa4b57..4e1764ea8 100644 --- a/glue/viewers/common/viewer.py +++ b/glue/viewers/common/viewer.py @@ -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.update_component_limits(components_changed) + except AttributeError: + pass + else: if layer_artist.layer is message.data: layer_artist.update() + try: + components_changed = message.components_changed + layer_artist.update_component_limits(components_changed) + except AttributeError: + pass def _update_subset(self, message): if message.attribute == 'style': From 311f8d5f90245492813a80c3102eb876ff24b62f Mon Sep 17 00:00:00 2001 From: Jonathan Foster Date: Tue, 19 Sep 2023 15:59:24 -0400 Subject: [PATCH 2/4] Make Data.update_components tell which components are changed and test --- glue/core/data.py | 2 +- glue/core/tests/test_components_changed.py | 45 ++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 glue/core/tests/test_components_changed.py diff --git a/glue/core/data.py b/glue/core/data.py index 5293a488b..43e275929 100644 --- a/glue/core/data.py +++ b/glue/core/data.py @@ -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: diff --git a/glue/core/tests/test_components_changed.py b/glue/core/tests/test_components_changed.py new file mode 100644 index 000000000..d729c97d7 --- /dev/null +++ b/glue/core/tests/test_components_changed.py @@ -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) From 40a9574454a19c615589319c4b03f3a94bfc95d4 Mon Sep 17 00:00:00 2001 From: Jonathan Foster Date: Tue, 19 Sep 2023 16:31:01 -0400 Subject: [PATCH 3/4] Add a test for a LayerArtist responding to updated components --- glue/viewers/common/tests/test_viewer.py | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/glue/viewers/common/tests/test_viewer.py b/glue/viewers/common/tests/test_viewer.py index a7d6ad412..904fef1d8 100644 --- a/glue/viewers/common/tests/test_viewer.py +++ b/glue/viewers/common/tests/test_viewer.py @@ -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(): @@ -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 update_component_limits(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 From 4c2c07a4a62d56dd563f810525fe327d51d55d10 Mon Sep 17 00:00:00 2001 From: Jonathan Foster Date: Thu, 21 Sep 2023 21:55:06 -0400 Subject: [PATCH 4/4] Use a generic _on_components_changed method --- glue/core/layer_artist.py | 4 ++-- glue/viewers/common/tests/test_viewer.py | 2 +- glue/viewers/common/viewer.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/glue/core/layer_artist.py b/glue/core/layer_artist.py index b4eb93ffc..aa24e956b 100644 --- a/glue/core/layer_artist.py +++ b/glue/core/layer_artist.py @@ -228,9 +228,9 @@ def _check_subset_state_changed(self): self._changed = True self._state = state - def update_component_limits(self, components_changed): + def _on_components_changed(self, components_changed): """ - Update component limits for this layer + React to a change to one or more of the components in this layer. """ pass diff --git a/glue/viewers/common/tests/test_viewer.py b/glue/viewers/common/tests/test_viewer.py index 904fef1d8..61f18de69 100644 --- a/glue/viewers/common/tests/test_viewer.py +++ b/glue/viewers/common/tests/test_viewer.py @@ -56,7 +56,7 @@ def test_viewer_update_data(): class CustomUpdateLayerArtist(LayerArtist): - def update_component_limits(self, components_changed): + def _on_components_changed(self, components_changed): self.called_component_limits = True self.num_components_changed = len(components_changed) diff --git a/glue/viewers/common/viewer.py b/glue/viewers/common/viewer.py index 4e1764ea8..71703c8d5 100644 --- a/glue/viewers/common/viewer.py +++ b/glue/viewers/common/viewer.py @@ -290,7 +290,7 @@ def _update_data(self, message): layer_artist.update() try: components_changed = message.components_changed - layer_artist.update_component_limits(components_changed) + layer_artist._on_components_changed(components_changed) except AttributeError: pass @@ -299,7 +299,7 @@ def _update_data(self, message): layer_artist.update() try: components_changed = message.components_changed - layer_artist.update_component_limits(components_changed) + layer_artist._on_components_changed(components_changed) except AttributeError: pass