Skip to content

Commit

Permalink
Viewer creator in app toolbar (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
kecnry authored Mar 11, 2024
1 parent db3548d commit 7b78df5
Show file tree
Hide file tree
Showing 19 changed files with 250 additions and 115 deletions.
7 changes: 6 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
0.2.0 - unreleased
0.3.0 - unreleased
------------------

* Ability to create additional viewers. [#94]

0.2.0 (02-26-2024)
------------------

* Clone viewer tool. [#74, #91]
Expand Down
45 changes: 36 additions & 9 deletions lcviz/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@
from glue.core.component_id import ComponentID
from glue.core.link_helpers import LinkSame
from jdaviz.core.helpers import ConfigHelper
from lcviz.viewers import TimeScatterView

__all__ = ['LCviz']

_default_time_viewer_reference_name = 'flux-vs-time'

custom_components = {'plugin-ephemeris-select': 'components/plugin_ephemeris_select.vue'}

# Register pure vue component. This allows us to do recursive component instantiation only in the
Expand All @@ -22,11 +21,7 @@


def _get_range_subset_bounds(self, subset_state, *args, **kwargs):
# Instead of overriding the jdaviz version of this method on jdaviz.Application,
# we could put in jdaviz by (1) checking if helper has a
# _default_time_viewer_reference_name, (2) using the LCviz version if so, and (3)
# using the jdaviz version otherwise.
viewer = self.get_viewer(self._jdaviz_helper._default_time_viewer_reference_name)
viewer = self._jdaviz_helper.default_time_viewer._obj
light_curve = viewer.data()[0]
reference_time = light_curve.meta['reference_time']
if viewer:
Expand Down Expand Up @@ -71,7 +66,7 @@ class LCviz(ConfigHelper):
'tab_headers': True},
'dense_toolbar': False,
'context': {'notebook': {'max_height': '600px'}}},
'toolbar': ['g-data-tools', 'g-subset-tools', 'lcviz-coords-info'],
'toolbar': ['g-data-tools', 'g-subset-tools', 'lcviz-viewer-creator', 'lcviz-coords-info'],
'tray': ['lcviz-metadata-viewer', 'flux-column',
'lcviz-plot-options', 'lcviz-subset-plugin',
'lcviz-markers', 'flatten', 'frequency-analysis', 'ephemeris',
Expand All @@ -86,7 +81,6 @@ class LCviz(ConfigHelper):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._default_time_viewer_reference_name = _default_time_viewer_reference_name

# override jdaviz behavior to support temporal subsets
self.app._get_range_subset_bounds = (
Expand Down Expand Up @@ -152,6 +146,39 @@ def get_data(self, data_label=None, cls=LightCurve, subset=None):
"""
return super()._get_data(data_label=data_label, mask_subset=subset, cls=cls)

@property
def default_time_viewer(self):
tvs = [viewer for vid, viewer in self.app._viewer_store.items()
if isinstance(viewer, TimeScatterView)]
if not len(tvs):
raise ValueError("no time viewers exist")
return tvs[0].user_api

@property
def _tray_tools(self):
"""
Access API objects for plugins in the app toolbar.
Returns
-------
plugins : dict
dict of plugin objects
"""
# TODO: provide user-friendly labels, user API, and move upstream to be public
# for now this is just useful for dev-debugging access to toolbar entries
from ipywidgets.widgets import widget_serialization
return {item['name']: widget_serialization['from_json'](item['widget'], None)
for item in self.app.state.tool_items}

def _get_clone_viewer_reference(self, reference):
base_name = reference.split("[")[0]
name = base_name
ind = 0
while name in self.viewers.keys():
ind += 1
name = f"{base_name}[{ind}]"
return name

def _phase_comp_lbl(self, component):
return f'phase:{component}'

Expand Down
15 changes: 7 additions & 8 deletions lcviz/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
from jdaviz.core.registries import data_parser_registry
import lightkurve

from lcviz.viewers import PhaseScatterView, TimeScatterView

__all__ = ["light_curve_parser"]


@data_parser_registry("light_curve_parser")
def light_curve_parser(app, file_obj, data_label=None, show_in_viewer=True, **kwargs):
time_viewer_reference_name = app._jdaviz_helper._default_time_viewer_reference_name

# load local FITS file from disk by its path:
if isinstance(file_obj, str) and os.path.exists(file_obj):
if data_label is None:
Expand Down Expand Up @@ -42,12 +42,11 @@ def light_curve_parser(app, file_obj, data_label=None, show_in_viewer=True, **kw
app.add_data(data, new_data_label)

if show_in_viewer:
app.add_data_to_viewer(time_viewer_reference_name, new_data_label)

# add to any known phase viewers
ephem_plugin = app._jdaviz_helper.plugins.get('Ephemeris', None)
if ephem_plugin is not None:
for viewer_id in ephem_plugin._obj.phase_viewer_ids:
# add to any known time/phase viewers
for viewer_id, viewer in app._viewer_store.items():
if isinstance(viewer, TimeScatterView):
app.add_data_to_viewer(viewer_id, new_data_label)
elif isinstance(viewer, PhaseScatterView):
app.add_data_to_viewer(viewer_id, new_data_label)


Expand Down
4 changes: 3 additions & 1 deletion lcviz/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from .binning.binning import * # noqa
from .viewer_creator.viewer_creator import * # noqa
from .coords_info.coords_info import * # noqa

from .binning.binning import * # noqa
from .ephemeris.ephemeris import * # noqa
from .export_plot.export_plot import * # noqa
from .flatten.flatten import * # noqa
Expand Down
34 changes: 21 additions & 13 deletions lcviz/plugins/binning/binning.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@

from lcviz.components import FluxColumnSelectMixin
from lcviz.events import EphemerisChangedMessage
from lcviz.helper import _default_time_viewer_reference_name
from lcviz.marks import LivePreviewBinning
from lcviz.parsers import _data_with_reftime
from lcviz.viewers import TimeScatterView, PhaseScatterView
from lcviz.components import EphemerisSelectMixin


Expand Down Expand Up @@ -68,7 +68,8 @@ def not_from_binning_plugin(data):
return data.meta.get('Plugin', None) != self.__class__.__name__
self.dataset.add_filter(not_from_binning_plugin)

self.hub.subscribe(self, ViewerAddedMessage, handler=self._set_results_viewer)
# TODO: viewer added also needs to repopulate marks
self.hub.subscribe(self, ViewerAddedMessage, handler=self._on_add_viewer)
self.hub.subscribe(self, ViewerRemovedMessage, handler=self._set_results_viewer)
self.hub.subscribe(self, EphemerisChangedMessage, handler=self._on_ephemeris_update)

Expand All @@ -95,15 +96,15 @@ def input_lc(self):
@property
def marks(self):
marks = {}
for id, viewer in self.app._viewer_store.items():
for viewer in self.app._viewer_store.values():
for mark in viewer.figure.marks:
if isinstance(mark, LivePreviewBinning):
marks[id] = mark
marks[viewer.reference] = mark
break
else:
mark = LivePreviewBinning(viewer, visible=self.is_active)
viewer.figure.marks = viewer.figure.marks + [mark]
marks[id] = mark
marks[viewer.reference] = mark
return marks

def _clear_marks(self):
Expand All @@ -129,25 +130,31 @@ def _set_results_viewer(self, event={}):

def viewer_filter(viewer):
if self.ephemeris_selected in self.ephemeris._manual_options:
return viewer.reference == _default_time_viewer_reference_name
if 'flux-vs-phase:' not in viewer.reference:
return isinstance(viewer, TimeScatterView)
if not isinstance(viewer, PhaseScatterView):
# ephemeris selected, but no active phase viewers
return False
return viewer.reference.split('flux-vs-phase:')[1] == self.ephemeris_selected
return viewer._ephemeris_component == self.ephemeris_selected

self.add_results.viewer.filters = [viewer_filter]

def _on_add_viewer(self, msg):
self._set_results_viewer()
self._live_update()

@observe('is_active', 'show_live_preview')
def _toggle_marks(self, event={}):
visible = self.show_live_preview and self.is_active

for viewer_id, mark in self.marks.items():
for viewer_ref, mark in self.marks.items():
if not visible:
this_visible = False
elif self.ephemeris_selected == 'No ephemeris':
this_visible = True
else:
this_visible = viewer_id.split(':')[-1] == self.ephemeris_selected
viewer = self.app.get_viewer(viewer_ref)
viewer_ephem = getattr(viewer, '_ephemeris_component', None)
this_visible = viewer_ephem == self.ephemeris_selected

mark.visible = this_visible

Expand Down Expand Up @@ -260,10 +267,11 @@ def bin(self, add_data=True):

if self.ephemeris_selected != 'No ephemeris':
# prevent phase axis from becoming a time axis:
viewer_id = self.ephemeris_plugin._obj.phase_viewer_id
pv = self.app.get_viewer(viewer_id)
ephemeris_plugin = self.app._jdaviz_helper.plugins['Ephemeris']
phase_comp_lbl = self.app._jdaviz_helper._phase_comp_lbl(self.ephemeris_selected)
pv.state.x_att = self.app._jdaviz_helper._component_ids[phase_comp_lbl]
phase_comp = self.app._jdaviz_helper._component_ids[phase_comp_lbl]
for pv in ephemeris_plugin._obj._get_phase_viewers(self.ephemeris_selected):
pv.state.x_att = phase_comp
# by resetting x_att, the preview marks may have dissappeared
self._live_update()

Expand Down
Loading

0 comments on commit 7b78df5

Please sign in to comment.