Source code for prospect.viewer.layouts

# Licensed under a 3-clause BSD style license - see LICENSE.rst
# -*- coding: utf-8 -*-
"""
=======================
prospect.viewer.layouts
=======================

Full bokeh layouts for prospect

"""

import bokeh.layouts as bl
from bokeh.models import CustomJS, Tabs, Panel
from bokeh.models.widgets import Div
import bokeh.events

from ..grid_thumbs import grid_thumbs

[docs]class ViewerLayout(object): ''' Layout of plots and widgets for prospect's GUI ''' # NB widget height / width are still partly hardcoded, but not arbitrary except for Spacers def __init__(self, plots, widgets, vi_widgets, with_vi_widgets=True): ''' plots : :class:`ViewerPlots` widgets : :class:`ViewerWidgets` vi_widgets : :class:`ViewerVIWidgets` ''' #- Main spectrum 'navigator' self.navigator = bl.row( bl.column(widgets.prev_button, width=widgets.navigation_button_width+15), bl.column(widgets.next_button, width=widgets.navigation_button_width+20), bl.column(widgets.ispectrumslider, width=plots.plot_width+(plots.plot_height//2)-(60*len(vi_widgets.vi_quality_labels)+2*widgets.navigation_button_width+35+100)), bl.column(widgets.ispec_input, width=100), background='#ececf9' ) #- Redshift widgets redshift_set_a = bl.row( bl.column(widgets.z_minus_button, width=widgets.z_button_width+15), bl.column(widgets.zslider, width=widgets.plot_widget_width-2*widgets.z_button_width-135), bl.column(widgets.z_plus_button, width=widgets.z_button_width) ) if with_vi_widgets: redshift_set_b = bl.row( bl.column(widgets.dzslider, width=widgets.plot_widget_width-235), bl.column(bl.Spacer(width=20)), bl.column(widgets.zreset_button, width=100) ) redshift_set_c = bl.column( bl.column(widgets.z_input, width=100), bl.column(vi_widgets.z_tovi_button, width=100) ) else: redshift_set_b = bl.column(widgets.dzslider, width=widgets.plot_widget_width-135) redshift_set_c = bl.column( bl.column(widgets.z_input, width=120), bl.column(widgets.zreset_button, width=120) ) self.redshift_widget_set = bl.row( bl.column( redshift_set_a, redshift_set_b ), bl.column(bl.Spacer(width=15)), redshift_set_c, background='#fff7e6' ) #- VI widgets if with_vi_widgets : self.navigator.children.insert(1, bl.column( vi_widgets.vi_quality_input, width=60*len(vi_widgets.vi_quality_labels) ) ) if vi_widgets.vi_countdown_toggle is None : vi_header_block = bl.column( Div(text="VI optional indications:"), width=300 ) else : vi_header_block = bl.row( bl.column( Div(text="VI optional indications :"), width=300 ), bl.column( vi_widgets.vi_countdown_toggle, width=200 ) ) self.vi_widget_set = bl.column( vi_header_block, bl.row( bl.column(vi_widgets.vi_issue_input, width=150), bl.column(vi_widgets.vi_z_input, width=150), bl.column(vi_widgets.vi_category_select, width=150) ), bl.row( bl.column(vi_widgets.vi_comment_input, width=300), bl.column(vi_widgets.vi_std_comment_select, width=200), ), bl.row( bl.column(vi_widgets.vi_name_input, width=200), bl.column(vi_widgets.vi_filename_input, width=300) ), bl.column(vi_widgets.save_vi_button, width=100), bl.column(vi_widgets.vi_table), bl.row( bl.column(vi_widgets.recover_vi_button, width=150), bl.column(vi_widgets.clear_vi_button, width=150) ), background='#f5f5f0' ) #- Plot-related widgets (ie. all the rest) self.plot_widget_set = bl.column( bl.column(widgets.smootherslider, width=widgets.plot_widget_width), self.redshift_widget_set, bl.column( Div(text="Pipeline fit: ") ), bl.column(widgets.table_z, width=widgets.plot_widget_width), bl.column(widgets.table_b, width=widgets.plot_widget_width), bl.column(widgets.table_c, width=widgets.plot_widget_width), bl.row( bl.column(widgets.speclines_button_group, width=200), bl.column(bl.Spacer(width=30)), bl.column(widgets.majorline_checkbox, width=120) ) ) if widgets.table_d is not None: self.plot_widget_set.children.insert(-1, bl.column(widgets.table_d, width=widgets.plot_widget_width)) if widgets.coaddcam_buttons is not None : waveframe_block = bl.row( bl.column(widgets.coaddcam_buttons, width=200), bl.column(bl.Spacer(width=30)), bl.column(widgets.waveframe_buttons, width=120) ) else: waveframe_block = bl.column(widgets.waveframe_buttons, width=120) self.plot_widget_set.children.append(waveframe_block) if widgets.model_select is not None : self.plot_widget_set.children.insert(4, bl.column(widgets.model_select, width=200)) #- Assemble all widgets if with_vi_widgets: self.full_widget_set = bl.column( bl.row( self.vi_widget_set, bl.column(bl.Spacer(width=40)), self.plot_widget_set ), bl.column(vi_widgets.vi_guideline_div, width=2*widgets.plot_widget_width) ) else: N = len(self.plot_widget_set.children) // 2 self.full_widget_set = bl.row( bl.column(self.plot_widget_set.children[0:N]), bl.column(self.plot_widget_set.children[N:]) ) self.main_bokehlayout = bl.column( bl.row(plots.fig, bl.column(plots.imfig, plots.zoomfig), bl.Spacer(width=20)), bl.row( bl.column(widgets.table_a, width=600), # plot_width - 200 bl.column(bl.Spacer(width=20)), bl.column(widgets.reset_plotrange_button, width = 120), bl.column(bl.Spacer(width=80)), bl.column(widgets.oii_zoom_button, width=80), bl.column(widgets.oii_undo_button, width=50), ), self.navigator, bl.column(bl.Spacer(height=10)), self.full_widget_set, sizing_mode='stretch_width' ) self.full_viewer = self.main_bokehlayout def add_thumb_tab(self, spectra, plots, widgets, nspec): self.ncols_grid = 5 # TODO un-hardcode self.miniplot_width = ( plots.plot_width + (plots.plot_height//2) ) // self.ncols_grid self.full_viewer = Tabs() titles = None # TODO define self.thumb_grid = grid_thumbs(spectra, self.miniplot_width, x_range=(plots.xmin,plots.xmax), ncols_grid=self.ncols_grid, titles=titles) tab1 = Panel(child = self.main_bokehlayout, title='Main viewer') tab2 = Panel(child = self.thumb_grid, title='Gallery') self.full_viewer.tabs=[ tab1, tab2 ] # Dirty trick : callback functions on thumbs need to be defined AFTER the full_viewer is implemented # Otherwise, at least one issue = no toolbar anymore for main fig. (apparently due to ispectrumslider in callback args) for i_spec in range(nspec) : self.thumb_callback = CustomJS(args=dict(full_viewer=self.full_viewer, i_spec=i_spec, ispectrumslider=widgets.ispectrumslider), code=""" full_viewer.active = 0 ispectrumslider.value = i_spec """) (self.thumb_grid.children[i_spec][0]).js_on_event(bokeh.events.DoubleTap, self.thumb_callback)
class StandaloneThumbLayout(object): ## Standalone grid of simple thumbs (to make lightweight pages showing all spectra) def __init__(self, spectra, plots, title): self.ncols_grid = 5 # TODO un-hardcode titles = None # TODO define self.miniplot_width = (plots.plot_width + (plots.plot_height//2) ) // self.ncols_grid self.thumb_grid = grid_thumbs(spectra, self.miniplot_width, x_range=(plots.xmin,plots.xmax), ncols_grid=self.ncols_grid, titles=titles) self.thumb_viewer = bl.column( bl.column( Div(text= " <h3> Thumbnail gallery for DESI spectra in "+title+" </h3>" + " <p> Click <a href='"+title+".html'>here</a> to access the spectral viewer corresponding to these spectra. </p>" ), width=plots.plot_width ), bl.column( self.thumb_grid ) )