Fix reset limits for profile viewer
Currently for the profile viewer (and by extension the specviz viewer in jdaviz) ``reset_limits`` does not do anything useful for the y axis when not in normalized mode. We should make sure that this works correctly.
diff --git a/CHANGES.md b/CHANGES.md
index 3129dd9c..64ebe3e9 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -7,6 +7,14 @@ v1.2.2 (unreleased)
 * Prevent resetting of image viewer limits when adding a dataset to the
   viewer. [#2232]
 
+* Fixed home button for resetting limits in profile viewer. [#2233]
+
+* Fixed a bug that caused the visibility checkbox of layers in the profile
+  viewer to not always correctly. [#2233]
+
+* Fixed a bug that caused the profile viewer limits to be unecessarily
+  changed every time a subset was updated or a dataset was added. [#2233]
+
 v1.2.1 (2021-08-24)
 -------------------
 
diff --git a/glue/plugins/dendro_viewer/layer_artist.py b/glue/plugins/dendro_viewer/layer_artist.py
index 3ae16f0c..db17bd22 100644
--- a/glue/plugins/dendro_viewer/layer_artist.py
+++ b/glue/plugins/dendro_viewer/layer_artist.py
@@ -81,7 +81,9 @@ class DendrogramLayerArtist(MatplotlibLayerArtist):
                 self.state.layer is None):
             return
 
-        changed = set() if force else self.pop_changed_properties()
+        # NOTE: we need to evaluate this even if force=True so that the cache
+        # of updated properties is up to date after this method has been called.
+        changed = self.pop_changed_properties()
 
         if force or any(prop in changed for prop in ('layer', 'height_att', 'parent_att', 'order_att')):
             self._update_dendrogram()
diff --git a/glue/viewers/histogram/layer_artist.py b/glue/viewers/histogram/layer_artist.py
index e892a78e..4d61f277 100644
--- a/glue/viewers/histogram/layer_artist.py
+++ b/glue/viewers/histogram/layer_artist.py
@@ -161,7 +161,9 @@ class HistogramLayerArtist(MatplotlibLayerArtist):
                 self.state.layer is None):
             return
 
-        changed = set() if force else self.pop_changed_properties()
+        # NOTE: we need to evaluate this even if force=True so that the cache
+        # of updated properties is up to date after this method has been called.
+        changed = self.pop_changed_properties()
 
         if force or any(prop in changed for prop in ('layer', 'x_att', 'hist_x_min', 'hist_x_max', 'hist_n_bin', 'x_log', 'y_log', 'normalize', 'cumulative')):
             self._calculate_histogram(reset=force)
diff --git a/glue/viewers/image/layer_artist.py b/glue/viewers/image/layer_artist.py
index a95fb496..6bd5c68a 100644
--- a/glue/viewers/image/layer_artist.py
+++ b/glue/viewers/image/layer_artist.py
@@ -180,7 +180,9 @@ class ImageLayerArtist(BaseImageLayerArtist):
         if self.uuid is None or self.state.attribute is None or self.state.layer is None:
             return
 
-        changed = set() if force else self.pop_changed_properties()
+        # NOTE: we need to evaluate this even if force=True so that the cache
+        # of updated properties is up to date after this method has been called.
+        changed = self.pop_changed_properties()
 
         if force or any(prop in changed for prop in ('layer', 'attribute',
                                                      'slices', 'x_att', 'y_att')):
@@ -339,7 +341,9 @@ class ImageSubsetLayerArtist(BaseImageLayerArtist):
         if self.state.layer is None:
             return
 
-        changed = set() if force else self.pop_changed_properties()
+        # NOTE: we need to evaluate this even if force=True so that the cache
+        # of updated properties is up to date after this method has been called.
+        changed = self.pop_changed_properties()
 
         if force or any(prop in changed for prop in ('layer', 'attribute', 'color',
                                                      'x_att', 'y_att', 'slices')):
diff --git a/glue/viewers/profile/layer_artist.py b/glue/viewers/profile/layer_artist.py
index 79338d1b..530c107f 100644
--- a/glue/viewers/profile/layer_artist.py
+++ b/glue/viewers/profile/layer_artist.py
@@ -1,12 +1,11 @@
 import sys
 import warnings
 
-import numpy as np
 from matplotlib.lines import Line2D
 
 
 from glue.core import BaseData
-from glue.utils import defer_draw, nanmin, nanmax
+from glue.utils import defer_draw
 from glue.viewers.profile.state import ProfileLayerState
 from glue.viewers.matplotlib.layer_artist import MatplotlibLayerArtist
 from glue.core.exceptions import IncompatibleAttribute, IncompatibleDataException
@@ -89,34 +88,6 @@ class ProfileLayerArtist(MatplotlibLayerArtist):
             # passing an empty list to plot_artist
             self.plot_artist.set_data([0.], [0.])
 
-        # TODO: the following was copy/pasted from the histogram viewer, maybe
-        # we can find a way to avoid duplication?
-
-        # We have to do the following to make sure that we reset the y_max as
-        # needed. We can't simply reset based on the maximum for this layer
-        # because other layers might have other values, and we also can't do:
-        #
-        #   self._viewer_state.y_max = max(self._viewer_state.y_max, result[0].max())
-        #
-        # because this would never allow y_max to get smaller.
-
-        if not self._viewer_state.normalize and len(y) > 0:
-
-            y_min = nanmin(y)
-            y_max = nanmax(y)
-            y_range = y_max - y_min
-
-            self.state._y_min = y_min - y_range * 0.1
-            self.state._y_max = y_max + y_range * 0.1
-
-            largest_y_max = max(getattr(layer, '_y_max', 0) for layer in self._viewer_state.layers)
-            if largest_y_max != self._viewer_state.y_max:
-                self._viewer_state.y_max = largest_y_max
-
-            smallest_y_min = min(getattr(layer, '_y_min', np.inf) for layer in self._viewer_state.layers)
-            if smallest_y_min != self._viewer_state.y_min:
-                self._viewer_state.y_min = smallest_y_min
-
         self.redraw()
 
     @defer_draw
@@ -154,10 +125,13 @@ class ProfileLayerArtist(MatplotlibLayerArtist):
                 self.state.layer is None):
             return
 
-        changed = set() if force else self.pop_changed_properties()
+        # NOTE: we need to evaluate this even if force=True so that the cache
+        # of updated properties is up to date after this method has been called.
+        changed = self.pop_changed_properties()
 
         if force or any(prop in changed for prop in ('layer', 'x_att', 'attribute', 'function', 'normalize', 'v_min', 'v_max', 'visible')):
             self._calculate_profile(reset=force)
+            force = True
 
         if force or any(prop in changed for prop in ('alpha', 'color', 'zorder', 'linewidth')):
             self._update_visual_attributes()
diff --git a/glue/viewers/profile/qt/tests/test_data_viewer.py b/glue/viewers/profile/qt/tests/test_data_viewer.py
index b65a0cee..fe1ad5ba 100644
--- a/glue/viewers/profile/qt/tests/test_data_viewer.py
+++ b/glue/viewers/profile/qt/tests/test_data_viewer.py
@@ -279,3 +279,42 @@ class TestProfileViewer(object):
         assert not viewer4.state.layers[2].visible
 
         ga.close()
+
+    def test_reset_limits(self):
+        self.viewer.add_data(self.data)
+        self.viewer.add_data(self.data2)
+        self.viewer.state.x_min = 0.2
+        self.viewer.state.x_max = 0.4
+        self.viewer.state.y_min = 0.3
+        self.viewer.state.y_max = 0.5
+        self.viewer.state.reset_limits()
+        assert self.viewer.state.x_min == 0
+        assert self.viewer.state.x_max == 4
+        assert self.viewer.state.y_min == 7
+        assert self.viewer.state.y_max == 23
+
+    def test_limits_unchanged(self):
+        # Make sure the limits don't change if a subset is created or another
+        # dataset added - they should only change if the reference data is changed
+        self.viewer.add_data(self.data)
+        self.viewer.state.x_min = 0.2
+        self.viewer.state.x_max = 0.4
+        self.viewer.state.y_min = 0.3
+        self.viewer.state.y_max = 0.5
+        self.viewer.add_data(self.data2)
+        assert self.viewer.state.x_min == 0.2
+        assert self.viewer.state.x_max == 0.4
+        assert self.viewer.state.y_min == 0.3
+        assert self.viewer.state.y_max == 0.5
+        roi = XRangeROI(0.9, 2.1)
+        self.viewer.apply_roi(roi)
+        assert self.viewer.state.x_min == 0.2
+        assert self.viewer.state.x_max == 0.4
+        assert self.viewer.state.y_min == 0.3
+        assert self.viewer.state.y_max == 0.5
+
+    def test_layer_visibility(self):
+        self.viewer.add_data(self.data)
+        assert self.viewer.layers[0].mpl_artists[0].get_visible() is True
+        self.viewer.state.layers[0].visible = False
+        assert self.viewer.layers[0].mpl_artists[0].get_visible() is False
diff --git a/glue/viewers/profile/state.py b/glue/viewers/profile/state.py
index 3967dac8..5a254460 100644
--- a/glue/viewers/profile/state.py
+++ b/glue/viewers/profile/state.py
@@ -53,7 +53,7 @@ class ProfileViewerState(MatplotlibDataViewerState):
         self.ref_data_helper = ManualDataComboHelper(self, 'reference_data')
 
         self.add_callback('layers', self._layers_changed)
-        self.add_callback('reference_data', self._reference_data_changed)
+        self.add_callback('reference_data', self._reference_data_changed, echo_old=True)
         self.add_callback('x_att', self._update_att)
         self.add_callback('normalize', self._reset_y_limits)
 
@@ -122,6 +122,25 @@ class ProfileViewerState(MatplotlibDataViewerState):
             with delay_callback(self, 'y_min', 'y_max'):
                 self.y_min = -0.1
                 self.y_max = +1.1
+        else:
+            y_min, y_max = np.inf, -np.inf
+            for layer in self.layers:
+                try:
+                    profile = layer.profile
+                except Exception:  # e.g. incompatible subset
+                    continue
+                if profile is not None:
+                    x, y = profile
+                    if len(y) > 0:
+                        y_min = min(y_min, nanmin(y))
+                        y_max = max(y_max, nanmax(y))
+            with delay_callback(self, 'y_min', 'y_max'):
+                if y_max > y_min:
+                    self.y_min = y_min
+                    self.y_max = y_max
+                else:
+                    self.y_min = 0
+                    self.y_max = 1
 
     def flip_x(self):
         """
@@ -135,7 +154,13 @@ class ProfileViewerState(MatplotlibDataViewerState):
         self._update_combo_ref_data()
 
     @defer_draw
-    def _reference_data_changed(self, *args):
+    def _reference_data_changed(self, before=None, after=None):
+
+        # A callback event for reference_data is triggered if the choices change
+        # but the actual selection doesn't - so we avoid resetting the WCS in
+        # this case.
+        if before is after:
+            return
 
         for layer in self.layers:
             layer.reset_cache()
@@ -161,6 +186,8 @@ class ProfileViewerState(MatplotlibDataViewerState):
 
                 self._update_att()
 
+        self.reset_limits()
+
     def _update_priority(self, name):
         if name == 'layers':
             return 2
diff --git a/glue/viewers/scatter/layer_artist.py b/glue/viewers/scatter/layer_artist.py
index c4a4590b..c2bc3da7 100644
--- a/glue/viewers/scatter/layer_artist.py
+++ b/glue/viewers/scatter/layer_artist.py
@@ -511,7 +511,9 @@ class ScatterLayerArtist(MatplotlibLayerArtist):
                 self.state.layer is None):
             return
 
-        changed = set() if force else self.pop_changed_properties()
+        # NOTE: we need to evaluate this even if force=True so that the cache
+        # of updated properties is up to date after this method has been called.
+        changed = self.pop_changed_properties()
 
         if force or len(changed & DATA_PROPERTIES) > 0:
             self._update_data()
['glue/viewers/profile/layer_artist.py', 'glue/viewers/scatter/layer_artist.py', 'glue/viewers/profile/qt/tests/test_data_viewer.py', 'glue/plugins/dendro_viewer/layer_artist.py', 'glue/viewers/profile/state.py', 'glue/viewers/histogram/layer_artist.py', 'CHANGES.md', 'glue/viewers/image/layer_artist.py']