diff --git a/CHANGES.rst b/CHANGES.rst index 31ab4b90..10fa9251 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,4 +1,9 @@ -0.1.0 - unreleased +0.2.0 - unreleased +------------------ + +* Clone viewer tool. [#74] + +0.1.0 (12-14-2023) ------------------ * Initial release of lcviz with support to import time-series light curves via lightkurve and diff --git a/lcviz/data/icons/viewer_clone.svg b/lcviz/data/icons/viewer_clone.svg new file mode 100644 index 00000000..3cb5c30f --- /dev/null +++ b/lcviz/data/icons/viewer_clone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lcviz/plugins/ephemeris/ephemeris.py b/lcviz/plugins/ephemeris/ephemeris.py index a96259ab..3b9f89d7 100644 --- a/lcviz/plugins/ephemeris/ephemeris.py +++ b/lcviz/plugins/ephemeris/ephemeris.py @@ -308,7 +308,8 @@ def vue_period_double(self, *args): self.period *= 2 def _check_if_phase_viewer_exists(self, *args): - self.phase_viewer_exists = self.phase_viewer_id in self.app.get_viewer_ids() + viewer_base_refs = [id.split('[')[0] for id in self.app.get_viewer_ids()] + self.phase_viewer_exists = self.phase_viewer_id in viewer_base_refs def _on_component_add(self, lbl): self.hub.broadcast(EphemerisComponentChangedMessage(old_lbl=None, new_lbl=lbl, diff --git a/lcviz/tests/test_viewers.py b/lcviz/tests/test_viewers.py index 66f15da6..71d3fca3 100644 --- a/lcviz/tests/test_viewers.py +++ b/lcviz/tests/test_viewers.py @@ -17,3 +17,13 @@ def test_reset_limits(helper, light_curve_like_kepler_quarter): tv.state._reset_y_limits() assert tv.state.y_min == orig_ylims[0] + + +def test_clone(helper, light_curve_like_kepler_quarter): + helper.load_data(light_curve_like_kepler_quarter) + + def_viewer = helper.viewers['flux-vs-time'] + assert def_viewer._obj._get_clone_viewer_reference() == 'flux-vs-time[1]' + + new_viewer = def_viewer._obj.clone_viewer() + assert new_viewer._obj._get_clone_viewer_reference() == 'flux-vs-time[2]' diff --git a/lcviz/tools.py b/lcviz/tools.py index 0ebbae63..badb5aa2 100644 --- a/lcviz/tools.py +++ b/lcviz/tools.py @@ -1,5 +1,26 @@ +import os + +from glue.config import viewer_tool +from glue.viewers.common.tool import Tool + from jdaviz.core.tools import SidebarShortcutPlotOptions, SidebarShortcutExportPlot +ICON_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), 'data', 'icons')) + # point to the lcviz-version of plot options instead of jdaviz's SidebarShortcutPlotOptions.plugin_name = 'lcviz-plot-options' SidebarShortcutExportPlot.plugin_name = 'lcviz-export-plot' + + +__all__ = ['ViewerClone'] + + +@viewer_tool +class ViewerClone(Tool): + icon = os.path.join(ICON_DIR, 'viewer_clone') + tool_id = 'lcviz:viewer_clone' + action_text = 'Clone viewer' + tool_tip = 'Clone this viewer' + + def activate(self): + self.viewer.clone_viewer() diff --git a/lcviz/viewers.py b/lcviz/viewers.py index e057939e..ee70f3b6 100644 --- a/lcviz/viewers.py +++ b/lcviz/viewers.py @@ -10,6 +10,7 @@ from astropy import units as u from astropy.time import Time +from jdaviz.core.events import NewViewerMessage from jdaviz.core.registries import viewer_registry from jdaviz.configs.default.plugins.viewers import JdavizViewerMixin from jdaviz.configs.specviz.plugins.viewers import SpecvizProfileView @@ -30,7 +31,7 @@ class TimeScatterView(JdavizViewerMixin, BqplotScatterView): ['jdaviz:boxzoom', 'jdaviz:xrangezoom', 'jdaviz:yrangezoom'], ['jdaviz:panzoom', 'jdaviz:panzoom_x', 'jdaviz:panzoom_y'], ['bqplot:xrange', 'bqplot:yrange', 'bqplot:rectangle'], - ['jdaviz:sidebar_plot', 'jdaviz:sidebar_export'] + ['lcviz:viewer_clone', 'jdaviz:sidebar_plot', 'jdaviz:sidebar_export'] ] default_class = LightCurve _state_cls = ScatterViewerState @@ -209,12 +210,46 @@ def apply_roi(self, roi, use_current=False): super().apply_roi(roi, use_current=use_current) + def _get_clone_viewer_reference(self): + base_name = self.reference.split("[")[0] + name = base_name + ind = 0 + while name in self.jdaviz_helper.viewers.keys(): + ind += 1 + name = f"{base_name}[{ind}]" + return name + + def clone_viewer(self): + name = self._get_clone_viewer_reference() + + self.jdaviz_app._on_new_viewer(NewViewerMessage(self.__class__, + data=None, + sender=self.jdaviz_app), + vid=name, name=name) + + this_viewer_item = self.jdaviz_app._get_viewer_item(self.reference) + this_state = self.state.as_dict() + for data in self.jdaviz_app.data_collection: + data_id = self.jdaviz_app._data_id_from_label(data.label) + visible = this_viewer_item['selected_data_items'].get(data_id, 'hidden') + self.jdaviz_app.set_data_visibility(name, data.label, visible == 'visible') + # TODO: don't revert color when adding same data to a new viewer + # (same happens when creating a phase-viewer from ephemeris plugin) + + new_viewer = self.jdaviz_helper.viewers[name]._obj + for k, v in this_state.items(): + if k in ('layers',): + continue + setattr(new_viewer.state, k, v) + + return new_viewer.user_api + @viewer_registry("lcviz-phase-viewer", label="phase-vs-time") class PhaseScatterView(TimeScatterView): @property def ephemeris_component(self): - return self.reference.split(':')[-1] + return self.reference.split('[')[0].split(':')[-1] def _set_plot_x_axes(self, dc, component_labels, light_curve): # setting of y_att will be handled by ephemeris plugin