Package qubx :: Module list_figure
[hide private]
[frames] | no frames]

Source Code for Module qubx.list_figure

   1  """Panel which generates high-quality figures of the selections in active List. 
   2   
   3  Copyright 2008-2014 Research Foundation State University of New York  
   4  This file is part of QUB Express.                                           
   5   
   6  QUB Express is free software; you can redistribute it and/or modify           
   7  it under the terms of the GNU General Public License as published by  
   8  the Free Software Foundation, either version 3 of the License, or     
   9  (at your option) any later version.                                   
  10   
  11  QUB Express is distributed in the hope that it will be useful,                
  12  but WITHOUT ANY WARRANTY; without even the implied warranty of        
  13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         
  14  GNU General Public License for more details.                          
  15   
  16  You should have received a copy of the GNU General Public License,    
  17  named LICENSE.txt, in the QUB Express program directory.  If not, see         
  18  <http://www.gnu.org/licenses/>.                                       
  19   
  20  """ 
  21   
  22  import cStringIO 
  23  import itertools 
  24  import os 
  25  import sys 
  26  import time 
  27  import traceback 
  28  import gobject 
  29  import cairo 
  30  import gtk 
  31  import qubx.settings 
  32  import qubx.GTK 
  33  import qubx.pyenv 
  34  import qubx.tree 
  35  import qubx.faces 
  36  import qubx.task 
  37  import qubx.dataGTK 
  38  import qubx.notebook 
  39  import qubx.notebookGTK 
  40  import qubx.toolspace 
  41   
  42  from gtk import gdk 
  43  from gtk import keysyms 
  44  from itertools import izip, count 
  45  from qubx.util_types import * 
  46  from qubx.accept import * 
  47  from qubx.dataGTK import UNSET_IDL 
  48  from qubx.GTK import pack_item, pack_space, pack_hsep, pack_vsep, pack_label, pack_button, pack_check, pack_radio, pack_scrolled, build_menuitem 
  49  from qubx.pyenvGTK import prompt_entry 
  50  from qubx.settings import Property, Propertied 
  51  from numpy import arange, array, zeros 
  52  import numpy 
  53  from qubx.task import Tasks 
  54  from qubx.toolspace import measure_string, SubLayer_Label, SubLayer_Check, SubLayer_Radio, COLOR_LABEL, COLOR_CLEAR, LAYER_BG 
  55   
  56  LINE_EMS = 1.7 
  57   
  58  LISTFIG_POSITION_HIDDEN = 0 
  59  LISTFIG_POSITION_ABOVE = 1 
  60  LISTFIG_POSITION_BELOW = 2 
  61  LISTFIG_POSITION_RIGHT = 3 
  62  LISTFIG_POSITION_FLOAT = 4 
  63   
  64   
  65  COLOR_LF_BG = ('list_figure.bg', (1.0, 1.0, 1.0, 1.0)) 
  66  qubx.toolspace.ColorInfo[COLOR_LF_BG[0]].label = "ListFigure background" 
  67  COLOR_LF_LABEL = ('list_figure.label', (0.0, 0.0, 0.0, 1.0)) 
  68  qubx.toolspace.ColorInfo[COLOR_LF_LABEL[0]].label = "ListFigure text" 
  69  COLOR_LF_TRACE = ('list_figure.trace', (0.0, 0.0, 0.0, 1.0)) 
  70  qubx.toolspace.ColorInfo[COLOR_LF_TRACE[0]].label = "ListFigure traces" 
  71  COLOR_LF_STIMULUS = ('list_figure.stimulus', (0.0, 0.0, 0.0, 1.0)) 
  72  qubx.toolspace.ColorInfo[COLOR_LF_STIMULUS[0]].label = "ListFigure stimulus traces" 
  73  COLOR_LF_IDL = ('list_figure.idl', (1.0, 0.0, 0.0, 0.9)) 
  74  qubx.toolspace.ColorInfo[COLOR_LF_IDL[0]].label = "ListFigure idealization" 
  75  COLOR_LF_STIMULUS_IDL = ('list_figure.stimulus.idl', (1.0, 0.0, 0.0, 0.9)) 
  76  qubx.toolspace.ColorInfo[COLOR_LF_STIMULUS_IDL[0]].label = "ListFigure stimulus idealization" 
  77  COLOR_LF_FIT = ('list_figure.fit', (1.0, 0.0, 0.0, 0.8)) 
  78  qubx.toolspace.ColorInfo[COLOR_LF_FIT[0]].label = "ListFigure fit curves" 
  79  COLOR_LF_ZERO = ('list_figure.zero', (0.0, 0.0, 0.0, 1.0)) 
  80  qubx.toolspace.ColorInfo[COLOR_LF_ZERO[0]].label = "ListFigure zero line (baseline)" 
  81  COLOR_LF_FG_TROUBLE = ('list_figure.trouble.fg', (1.0, 1.0, 0.25, 0.9)) 
  82  qubx.toolspace.ColorInfo[COLOR_LF_FG_TROUBLE[0]].label = 'ListFigure options attention foreground' 
  83  COLOR_LF_BG_TROUBLE = ('list_figure.trouble.bg', (0.6, 0.0, 0.0, 0.7)) 
  84  qubx.toolspace.ColorInfo[COLOR_LF_BG_TROUBLE[0]].label = 'ListFigure options attention background' 
  85  COLOR_LF_OPTION = ('list_figure.option', (1, .8, .8, 1)) 
  86  qubx.toolspace.ColorInfo[COLOR_LF_OPTION[0]].label = 'ListFigure option text' 
  87   
  88  COLOR_LF_TAB_SEL = ('list_figure.selected_tab', (0.0, 0.0, 0.0, 1.0)) 
  89  qubx.toolspace.ColorInfo[COLOR_LF_TAB_SEL[0]].label = "ListFigure selected tab" 
  90  COLOR_LF_TAB = LAYER_BG 
  91   
  92  @Propertied( # Layout: 
  93              Property('output_w', 1600, 'Output width (pixels)'), 
  94              Property('output_h', 900, 'Output height (pixels)'), 
  95              Property('margin_h', 0.05, 'Size of left and right margins, as a proportion of total width'), 
  96              Property('margin_v', 0.05, 'Size of top and bottom margins, as a proportion of total height'), 
  97              Property('tracepad_h', 0.02, 'Size of whitespace to left and right of each trace, as a proportion of trace width'), 
  98              Property('tracepad_v', 0.02, 'Size of whitespace above and below each trace, as a proportion of trace height'), 
  99              Property('font_face', 'sans-serif', 'Label font'), 
 100              Property('font_weight', 800, 'Label font weight (1 <= font_weight <= 1000)'), 
 101              Property('font_height', 0.75, 'Label font height as a proportion of line height (excluding descenders)'), 
 102              Property('font_max_width', 0.25, 'Maximum label width as a proportion of line width'), 
 103              # Draw: 
 104              Property('draw_exact', 1, 'True to draw each datapoint, False to draw vertical distributions at each pixel'), 
 105              Property('dot_size', 0.02, 'Size of datapoint dots (or 0.0 to disable) as a proportion of trace height'), 
 106              Property('line_width', 0.01, 'Thickness of lines between datapoints (or 0.0 to disable) as a proportion of trace height'), 
 107              Property('line_opacity', 0.8, 'Opacity of lines (0.0 <= line_opacity <= 1.0)'), 
 108              Property('zero_line_width', 0.01, 'Thickness of baseline at y=0'), 
 109              # colors are shown in Draw panel but controlled by global Appearance.colors 
 110              # Traces: 
 111              Property('multi_file', 0, 'True to draw traces from multiple open files'), 
 112              Property('multi_file_group', 1, 'Group in Data table of files to be drawn'), 
 113              Property('trace_label_format', '%(Label)s', 'Format string for trace labels, using named fields of the List table'), 
 114              Property('trace_label_baseline', 0.1, 'distance between trace bottom and label bottom, as a proportion of trace height'), 
 115              Property('trace_sampled', 1, 'True to draw sampled data'), 
 116              Property('trace_idealized', 1, 'True to draw idealized data'), 
 117              Property('trace_idealized_above', 1, 'True to draw idealized data on its own line'), 
 118              Property('trace_fit', 1, 'True to draw fit curves'), 
 119              Property('trace_zero_line', 0, 'True to draw a line at y=0'), 
 120              # Stimulus: 
 121              Property('stimulus_label_format', '%(Name)s [%(Units)s]', 'Format string for stimulus labels, using named fields of the Scope table'), 
 122              Property('stimulus_label_baseline', 0.1, 'distance between stimulus bottom and label bottom, as a proportion of trace height'), 
 123              Property('stimulus_position', LISTFIG_POSITION_BELOW, 'location of stimulus display: LISTFIG_POSITION_HIDDEN, LISTFIG_POSITION_ABOVE, or LISTFIG_POSITION_BELOW', 
 124                       value_names={LISTFIG_POSITION_HIDDEN: "LISTFIG_POSITION_HIDDEN", LISTFIG_POSITION_ABOVE: "LISTFIG_POSITION_ABOVE", LISTFIG_POSITION_BELOW: "LISTFIG_POSITION_BELOW"}), 
 125              Property('stimulus_trace', 0, 'index (in List) of trace whose stimulus is to be displayed'), 
 126              Property('stimulus_sampled', 1, 'True to draw sampled data'), 
 127              Property('stimulus_idealized', 1, 'True to draw idealized data'), 
 128              Property('stimulus_zero_line', 0, 'True to draw a line at y=0'), 
 129              # Zoom: e.g. zoom_min_Current, zoom_max_Current 
 130              # Scale Bars: 
 131              Property('scalebar_font_height', 0.2, 'as a proportion of trace height'), 
 132              Property('scalebar_number_format', '%.1g', 'Format string for scalebar numbers, e.g. %.1g'), 
 133              Property('scalebar_position', LISTFIG_POSITION_RIGHT, 'location of scale bars: LISTFIG_POSITION_HIDDEN, LISTFIG_POSITION_ABOVE, LISTFIG_POSITION_BELOW, LISTFIG_POSITION_RIGHT, or LISTFIG_POSITION_FLOAT', 
 134                       value_names={LISTFIG_POSITION_HIDDEN: "LISTFIG_POSITION_HIDDEN", LISTFIG_POSITION_ABOVE: "LISTFIG_POSITION_ABOVE", LISTFIG_POSITION_BELOW: "LISTFIG_POSITION_BELOW", 
 135                                    LISTFIG_POSITION_RIGHT: "LISTFIG_POSITION_RIGHT", LISTFIG_POSITION_FLOAT: "LISTFIG_POSITION_FLOAT"}), 
 136              Property('scalebar_vertical_labels', 1, 'False to write all text horizontally'), 
 137              Property('scalebar_timebar', 0, 'True to draw a separate scale bar for the Time axis'), 
 138              Property('scalebar_timebar_x', 0.5, 'Horizontal position (center) of timebar, as a proportion of figure width'), 
 139              Property('scalebar_timebar_y', 0.9, 'Vertical position (center) of timebar, as a proportion of figure height'), 
 140              Property('scalebar_timebar_autodim', 1, 'False to specify dimension (length) manually'), 
 141              Property('scalebar_timebar_dim', 1.0, 'Length (in seconds) of timebar, if not scalebar_timebar_autodim') 
 142              ) 
143 -class ListFigureFace(qubx.faces.Face):
144 """ 145 146 Panel which generates high-quality figures from the selection list. 147 148 """ 149 150 __explore_featured = ['plot', 'layHeaders', 'headers', 'subPresets', 'panels', 'subNotebook', 'nbPicture', 'nbScalebars', 151 'scalebar_signal', 'update', 'nb_scalebars_draw', 'prepare_draw'] 152
153 - def __init__(self, name, global_name=""):
154 """@param name: caption for this panel's tab""" 155 qubx.faces.Face.__init__(self, name, global_name) 156 self.__ref = Reffer() 157 self.set_size_request(100, 70) 158 159 self.plot = pack_item(qubx.toolspace.ToolSpace(), self, expand=True) 160 self.plot.OnDraw += self.__ref(self.__onDraw) 161 self.plot.OnPress += self.__ref(bind(self.__onClickHeader, None)) # clicking outside prefs panel hides it 162 self.layHeaders = qubx.toolspace.Layer(x=2, y=0.5, w=6*13+12+2+2, h=LINE_EMS, cBG=COLOR_CLEAR) 163 self.plot.add_layer(self.layHeaders) 164 self.headers = [SubLayer_Label('Layout', 0, 1, x=1, y=0, w=11, h=LINE_EMS, cBG=COLOR_LF_TAB, action=self.__ref(bind(self.__onClickHeader, 0))), 165 SubLayer_Label('Draw', 0, 1, x=1+1*13, y=0, w=11, h=LINE_EMS, cBG=COLOR_LF_TAB, action=self.__ref(bind(self.__onClickHeader, 1))), 166 SubLayer_Label('Traces', 0, 1, x=1+2*13, y=0, w=11, h=LINE_EMS, cBG=COLOR_LF_TAB, action=self.__ref(bind(self.__onClickHeader, 2))), 167 SubLayer_Label('Stimulus', 0, 1, x=1+3*13, y=0, w=11, h=LINE_EMS, cBG=COLOR_LF_TAB, action=self.__ref(bind(self.__onClickHeader, 3))), 168 SubLayer_Label('Zoom', 0, 1, x=1+4*13, y=0, w=11, h=LINE_EMS, cBG=COLOR_LF_TAB, action=self.__ref(bind(self.__onClickHeader, 4))), 169 SubLayer_Label('Scale Bars', 0, 1, x=1+5*13, y=0, w=11, h=LINE_EMS, cBG=COLOR_LF_TAB, action=self.__ref(bind(self.__onClickHeader, 5)))] 170 for hdr in self.headers: 171 self.layHeaders.add_sublayer(hdr) 172 self.subPresets = qubx.toolspace.SubLayer_Popup(color=COLOR_LABEL, on_popup=self.__ref(self.__onPresetsPopup), caption=' Presets...', 173 x=-16, y=0, w=12, h=LINE_EMS, cBG=COLOR_LF_TAB) 174 self.headers.append(self.layHeaders.add_sublayer(SubLayer_Label('?', 0, 1, x=-3.5, y=0, w=1.5, h=LINE_EMS, border=1, cBG=COLOR_LF_TAB, 175 action=self.__ref(bind(self.__onClickHeader, 6))))) 176 self.subPresets.popup = gtk.Menu() 177 self.layHeaders.add_sublayer(self.subPresets) 178 179 self.__scalebar_signal = None 180 self.propertied_connect_settings('ListFigure') 181 182 self.panels = [] 183 self.__last_panel = None 184 pw = self.layHeaders.rq_w - 2 # margin 185 # Layout: 186 panel = qubx.toolspace.Layer(x=self.layHeaders.rq_x, y=self.layHeaders.rq_y+self.layHeaders.rq_h, w=self.layHeaders.rq_w, h=(10+4)*LINE_EMS) 187 self.panels.append(qubx.toolspace.LayerSet([panel])) 188 panel.add_sublayer(SubLayer_Label('Notebook output Width:', -1, 1, x=1, y=LINE_EMS, w=pw/2, h=LINE_EMS)) 189 self.subOutputW = panel.add_sublayer(SubLayer_Label('%i'%self.output_w, -1, 1, x=1+pw/2, y=LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 190 tooltip="Width in pixels of the output from this panel's Notebook menu", 191 action=self.__ref(bind(self.__onClickEditProperty, 'output_w', 'Notebook output Width:', acceptIntGreaterThan(0))), 192 scroll=self.__ref(bind_with_args(self.__onScrollIntProperty, 'output_w', 1, None)))) 193 panel.add_sublayer(SubLayer_Label('Notebook output Height:', -1, 1, x=1, y=2*LINE_EMS, w=pw/2, h=LINE_EMS)) 194 self.subOutputH = panel.add_sublayer(SubLayer_Label('%i'%self.output_h, -1, 1, x=1+pw/2, y=2*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 195 tooltip="Height in pixels of the output from this panel's Notebook menu", 196 action=self.__ref(bind(self.__onClickEditProperty, 'output_h', 'Notebook output Height:', acceptIntGreaterThan(0))), 197 scroll=self.__ref(bind_with_args(self.__onScrollIntProperty, 'output_h', 1, None)))) 198 panel.add_sublayer(SubLayer_Label('Margin (horizontal):', -1, 1, x=1, y=3*LINE_EMS, w=pw/2, h=LINE_EMS)) 199 self.subMarginH = panel.add_sublayer(SubLayer_Label('%.2f'%self.margin_h, -1, 1, x=1+pw/2, y=3*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 200 tooltip="Size of left and right margins, as a proportion of total width", 201 action=self.__ref(bind(self.__onClickEditProperty, 'margin_h', 'Margin (horizontal):', acceptFloatBetween(0.0, 0.45))), 202 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'margin_h', 0.0, 0.45)))) 203 panel.add_sublayer(SubLayer_Label('Margin (vertical):', -1, 1, x=1, y=4*LINE_EMS, w=pw/2, h=LINE_EMS)) 204 self.subMarginV = panel.add_sublayer(SubLayer_Label('%.2f'%self.margin_v, -1, 1, x=1+pw/2, y=4*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 205 tooltip="Size of top and bottom margins, as a proportion of total width", 206 action=self.__ref(bind(self.__onClickEditProperty, 'margin_v', 'Margin (vertical):', acceptFloatBetween(0.0, 0.45))), 207 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'margin_v', 0.0, 0.45)))) 208 panel.add_sublayer(SubLayer_Label('Trace padding (horizontal):', -1, 1, x=1, y=5*LINE_EMS, w=pw/2, h=LINE_EMS)) 209 self.subTracepadH = panel.add_sublayer(SubLayer_Label('%.2f'%self.tracepad_h, -1, 1, x=1+pw/2, y=5*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 210 tooltip="Size of whitespace to left and right of each trace, as a proportion of trace width", 211 action=self.__ref(bind(self.__onClickEditProperty, 'tracepad_h', 'Trace padding (horizontal):', acceptFloatBetween(0.0, 0.45))), 212 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'tracepad_h', 0.0, 0.45)))) 213 panel.add_sublayer(SubLayer_Label('Trace padding (vertical):', -1, 1, x=1, y=6*LINE_EMS, w=pw/2, h=LINE_EMS)) 214 self.subTracepadV = panel.add_sublayer(SubLayer_Label('%.2f'%self.tracepad_v, -1, 1, x=1+pw/2, y=6*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 215 tooltip="Size of whitespace above and below each trace, as a proportion of trace width", 216 action=self.__ref(bind(self.__onClickEditProperty, 'tracepad_v', 'Trace padding (vertical):', acceptFloatBetween(0.0, 0.45))), 217 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'tracepad_v', 0.0, 0.45)))) 218 panel.add_sublayer(SubLayer_Label('Font:', -1, 1, x=1, y=7*LINE_EMS, w=pw/2, h=LINE_EMS)) 219 self.subFontFace = panel.add_sublayer(SubLayer_Label(self.font_face, -1, 1, x=1+pw/2, y=7*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 220 tooltip="Font name for labels (default: sans-serif)", 221 action=self.__ref(self.__onClickFontFace))) 222 panel.add_sublayer(SubLayer_Label('Font weight:', -1, 1, x=1, y=8*LINE_EMS, w=pw/2, h=LINE_EMS)) 223 self.subFontWeight = panel.add_sublayer(SubLayer_Label('%i'%self.font_weight, -1, 1, x=1+pw/2, y=8*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 224 tooltip="Boldness of label font, between 1 and 1000", 225 action=self.__ref(bind(self.__onClickEditProperty, 'font_weight', 'Font weight:', acceptIntBetween(1, 1000))), 226 scroll=self.__ref(bind_with_args(self.__onScrollIntProperty, 'font_weight', 1, 1000)))) 227 panel.add_sublayer(SubLayer_Label('Font height:', -1, 1, x=1, y=9*LINE_EMS, w=pw/2, h=LINE_EMS)) 228 self.subFontHeight = panel.add_sublayer(SubLayer_Label('%.2f'%self.font_height, -1, 1, x=1+pw/2, y=9*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 229 tooltip="as a proportion of line height", 230 action=self.__ref(bind(self.__onClickEditProperty, 'font_height', 'Font height:', acceptFloatBetween(0.0, 1.0))), 231 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'font_height', 0.0, 1.0)))) 232 panel.add_sublayer(SubLayer_Label('Max label width:', -1, 1, x=1, y=10*LINE_EMS, w=pw/2, h=LINE_EMS)) 233 self.subFontMaxWidth = panel.add_sublayer(SubLayer_Label('%.2f'%self.font_max_width, -1, 1, x=1+pw/2, y=10*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 234 tooltip="as a proportion of line width", 235 action=self.__ref(bind(self.__onClickEditProperty, 'font_max_width', 'Max label width:', acceptFloatBetween(0.0, 1.0))), 236 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'font_max_width', 0.0, 1.0)))) 237 panel.add_sublayer(SubLayer_Label('Close', 0, 1, x=pw/2, y=12*LINE_EMS, w=10, h=1.2*LINE_EMS, cBG=LAYER_BG, border=1, 238 action=self.__ref(bind(self.__onClickHeader, None)))) 239 240 # Draw: 241 colors = [COLOR_LF_BG, COLOR_LF_LABEL, COLOR_LF_TRACE, COLOR_LF_IDL, COLOR_LF_FIT, COLOR_LF_STIMULUS, COLOR_LF_STIMULUS_IDL, COLOR_LF_ZERO] 242 panel = qubx.toolspace.Layer(x=self.layHeaders.rq_x, y=self.layHeaders.rq_y+self.layHeaders.rq_h, w=self.layHeaders.rq_w, h=(5+len(colors)+4)*LINE_EMS) 243 self.panels.append(qubx.toolspace.LayerSet([panel])) 244 self.subDrawExact = SubLayer_Check(caption="Draw every point", tooltip="disables approximate drawing at lower resolutions", 245 active=self.draw_exact, color=COLOR_LF_OPTION, 246 x=1, y=LINE_EMS, w=pw, h=LINE_EMS) 247 self.subDrawExact.OnToggle += self.__ref(bind(self.__onToggleProperty, 'draw_exact')) 248 panel.add_sublayer(self.subDrawExact) 249 panel.add_sublayer(SubLayer_Label('Dot size:', -1, 1, x=1, y=2*LINE_EMS, w=pw/2, h=LINE_EMS)) 250 self.subDotSize = panel.add_sublayer(SubLayer_Label('%.2f'%self.dot_size, -1, 1, x=1+pw/2, y=2*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 251 tooltip="as a proportion of trace height (or 0.0 for no dots)", 252 action=self.__ref(bind(self.__onClickEditProperty, 'dot_size', 'Dot size:', acceptFloatBetween(0.0, 0.3))), 253 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'dot_size', 0.0, 0.3)))) 254 panel.add_sublayer(SubLayer_Label('Line width:', -1, 1, x=1, y=3*LINE_EMS, w=pw/2, h=LINE_EMS)) 255 self.subLineWidth = panel.add_sublayer(SubLayer_Label('%.2f'%self.line_width, -1, 1, x=1+pw/2, y=3*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 256 tooltip="as a proportion of trace height (or 0.0 for no lines)", 257 action=self.__ref(bind(self.__onClickEditProperty, 'line_width', 'Line width:', acceptFloatBetween(0.0, 0.3))), 258 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'line_width', 0.0, 0.3)))) 259 panel.add_sublayer(SubLayer_Label('Line opacity:', -1, 1, x=1, y=4*LINE_EMS, w=pw/2, h=LINE_EMS)) 260 self.subLineOpacity = panel.add_sublayer(SubLayer_Label('%.2f'%self.line_opacity, -1, 1, x=1+pw/2, y=4*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 261 tooltip="between 0.0 (transparent) and 1.0 (opaque)", 262 action=self.__ref(bind(self.__onClickEditProperty, 'line_opacity', 'Line opacity:', acceptFloatBetween(0.0, 1.0))), 263 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'line_opacity', 0.0, 1.0)))) 264 panel.add_sublayer(SubLayer_Label('Zero line (baseline) width:', -1, 1, x=1, y=5*LINE_EMS, w=pw/2, h=LINE_EMS)) 265 self.subZeroLineWidth = panel.add_sublayer(SubLayer_Label('%.2f'%self.zero_line_width, -1, 1, x=1+pw/2, y=5*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 266 tooltip="as a proportion of trace height (or 0.0 for no baseline)", 267 action=self.__ref(bind(self.__onClickEditProperty, 'zero_line_width', 'Zero line width:', acceptFloatBetween(0.0, 0.3))), 268 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'zero_line_width', 0.0, 0.3)))) 269 iline = 6 270 for color in colors: 271 panel.add_sublayer(SubLayer_Label(qubx.toolspace.ColorInfo[color[0]].label, -1, 1, x=1, y=iline*LINE_EMS, w=pw/2, h=LINE_EMS)) 272 panel.add_sublayer(SubLayer_Label("<click to edit>", 0, 1, x=1+pw/2, y=iline*LINE_EMS, w=pw/2, h=LINE_EMS, cBG=color, border=1, 273 tooltip="these colors aren't part of this panel's Presets; instead they are controlled by the Presets in Admin:Settings", 274 action=self.__ref(bind(qubx.toolspace.EditOneColor, color)))) 275 iline += 1 276 panel.add_sublayer(SubLayer_Label('Close', 0, 1, x=pw/2, y=(iline+1)*LINE_EMS, w=10, h=1.2*LINE_EMS, cBG=LAYER_BG, border=1, 277 action=self.__ref(bind(self.__onClickHeader, None)))) 278 279 # Traces: 280 panel = qubx.toolspace.Layer(x=self.layHeaders.rq_x, y=self.layHeaders.rq_y+self.layHeaders.rq_h, w=self.layHeaders.rq_w, h=(9+4)*LINE_EMS) 281 self.panels.append(qubx.toolspace.LayerSet([panel])) 282 self.subMultiFile = SubLayer_Check(caption="Multi-file", tooltip="Draws traces from all files in Data table with this Group number", 283 active=self.multi_file, color=COLOR_LF_OPTION, 284 x=1, y=LINE_EMS, w=pw/2, h=LINE_EMS) 285 self.subMultiFile.OnToggle += self.__ref(bind(self.__onToggleProperty, 'multi_file')) 286 panel.add_sublayer(self.subMultiFile) 287 panel.add_sublayer(SubLayer_Label('Group:', 1, 1, x=1, y=LINE_EMS, w=pw/2-1, h=LINE_EMS, color=COLOR_LF_OPTION, hover_color=COLOR_LF_OPTION)) 288 self.subMultiFileGroup = panel.add_sublayer(SubLayer_Label('%i'%self.multi_file_group, -1, 1, x=1+pw/2, y=LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 289 tooltip="To pick which open data files to draw, set their Group in the Data table", 290 action=self.__ref(bind(self.__onClickEditProperty, 'multi_file_group', 'Group:', acceptIntGreaterThan(-1))), 291 scroll=self.__ref(bind_with_args(self.__onScrollIntProperty, 'multi_file_group', 0, None)))) 292 panel.add_sublayer(SubLayer_Label('Trace label format:', -1, 1, x=1, y=2*LINE_EMS, w=pw/2, h=LINE_EMS)) 293 self.subTraceLabelFormat = panel.add_sublayer(SubLayer_Label(self.trace_label_format, -1, 1, x=1+pw/2, y=2*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 294 tooltip="template using fields of the List table by name, e.g. %(Label)s", 295 action=self.__ref(bind(self.__onClickEditProperty, 'trace_label_format', 'Trace label format:')))) 296 panel.add_sublayer(SubLayer_Label('Trace label baseline:', -1, 1, x=1, y=3*LINE_EMS, w=pw/2, h=LINE_EMS)) 297 self.subTraceLabelBaseline = panel.add_sublayer(SubLayer_Label('%.2f'%self.trace_label_baseline, -1, 1, x=1+pw/2, y=3*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 298 tooltip="distance from trace bottom, as a proportion of trace height'", 299 action=self.__ref(bind(self.__onClickEditProperty, 'trace_label_baseline', 'Trace label baseline:', acceptFloatBetween(0.0, 1.0))), 300 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'trace_label_baseline', 0.0, 1.0)))) 301 panel.add_sublayer(SubLayer_Label('Draw signals:', -1, 1, x=1, y=4*LINE_EMS, w=pw/2, h=LINE_EMS)) 302 self.subTraceSignals = panel.add_sublayer(SubLayer_Label("", -1, 1, x=1+pw/2, y=4*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 303 tooltip="click to pick which signals are drawn", 304 action=self.__ref(self.__onClickTraceSignals))) 305 self.subTraceSampled = SubLayer_Check(caption="Draw sampled data", tooltip="enables drawing sampled data points", 306 active=self.trace_sampled, color=COLOR_LF_OPTION, 307 x=1, y=5*LINE_EMS, w=pw, h=LINE_EMS) 308 self.subTraceSampled.OnToggle += self.__ref(bind(self.__onToggleProperty, 'trace_sampled')) 309 panel.add_sublayer(self.subTraceSampled) 310 self.subTraceIdealized = SubLayer_Check(caption="Draw idealized data", tooltip="enables drawing idealized events", 311 active=self.trace_idealized, color=COLOR_LF_OPTION, 312 x=1, y=6*LINE_EMS, w=pw, h=LINE_EMS) 313 self.subTraceIdealized.OnToggle += self.__ref(bind(self.__onToggleProperty, 'trace_idealized')) 314 panel.add_sublayer(self.subTraceIdealized) 315 self.subTraceIdealizedAbove = SubLayer_Check(caption="idealized data above sampled", tooltip="else drawn on top of sampled data", 316 active=self.trace_idealized_above, color=COLOR_LF_OPTION, 317 x=1, y=7*LINE_EMS, w=pw, h=LINE_EMS) 318 self.subTraceIdealizedAbove.OnToggle += self.__ref(bind(self.__onToggleProperty, 'trace_idealized_above')) 319 panel.add_sublayer(self.subTraceIdealizedAbove) 320 self.subTraceFit = SubLayer_Check(caption="Draw fit curves", tooltip="enables drawing fit curves from MacRates and least-squares curve fitting", 321 active=self.trace_fit, color=COLOR_LF_OPTION, 322 x=1, y=8*LINE_EMS, w=pw, h=LINE_EMS) 323 self.subTraceFit.OnToggle += self.__ref(bind(self.__onToggleProperty, 'trace_fit')) 324 panel.add_sublayer(self.subTraceFit) 325 self.subTraceZeroLine = SubLayer_Check(caption="Draw zero line (baseline)", tooltip="draws a line at y=0", 326 active=self.trace_zero_line, color=COLOR_LF_OPTION, 327 x=1, y=9*LINE_EMS, w=pw, h=LINE_EMS) 328 self.subTraceZeroLine.OnToggle += self.__ref(bind(self.__onToggleProperty, 'trace_zero_line')) 329 panel.add_sublayer(self.subTraceZeroLine) 330 panel.add_sublayer(SubLayer_Label('Close', 0, 1, x=pw/2, y=11*LINE_EMS, w=10, h=1.2*LINE_EMS, cBG=LAYER_BG, border=1, 331 action=self.__ref(bind(self.__onClickHeader, None)))) 332 333 # Stimulus 334 panel = qubx.toolspace.Layer(x=self.layHeaders.rq_x, y=self.layHeaders.rq_y+self.layHeaders.rq_h, w=self.layHeaders.rq_w, h=(8+4)*LINE_EMS) 335 self.panels.append(qubx.toolspace.LayerSet([panel])) 336 panel.add_sublayer(SubLayer_Label('Signal label format:', -1, 1, x=1, y=LINE_EMS, w=pw/2, h=LINE_EMS)) 337 self.subStimulusLabelFormat = panel.add_sublayer(SubLayer_Label(self.stimulus_label_format, -1, 1, x=1+pw/2, y=LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 338 tooltip="template using fields of the Scope table by name, e.g. %(Name)s [%(Units)s]", 339 action=self.__ref(bind(self.__onClickEditProperty, 'stimulus_label_format', 'Signal label format:')))) 340 panel.add_sublayer(SubLayer_Label('Stimulus label baseline:', -1, 1, x=1, y=2*LINE_EMS, w=pw/2, h=LINE_EMS)) 341 self.subStimulusLabelBaseline = panel.add_sublayer(SubLayer_Label('%.2f'%self.stimulus_label_baseline, -1, 1, x=1+pw/2, y=2*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 342 tooltip="distance from trace bottom, as a proportion of trace height'", 343 action=self.__ref(bind(self.__onClickEditProperty, 'stimulus_label_baseline', 'Stimulus label baseline:', acceptFloatBetween(0.0, 1.0))), 344 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'stimulus_label_baseline', 0.0, 1.0)))) 345 panel.add_sublayer(SubLayer_Label('Stimulus position:', -1, 1, x=1, y=3*LINE_EMS, w=pw/4, h=LINE_EMS)) 346 rad = self.subStimulusPositionAbove = SubLayer_Radio(label="Top", x=pw/4, y=3*LINE_EMS, w=pw/4, h=LINE_EMS, 347 active=(self.stimulus_position == LISTFIG_POSITION_ABOVE), 348 color=COLOR_LF_OPTION, fill_color=COLOR_LF_OPTION, 349 on_radio=self.__ref(self.__onRadioStimulusPosition), pos=LISTFIG_POSITION_ABOVE) 350 panel.add_sublayer(rad) 351 self.subStimulusPositionBelow = panel.add_sublayer(SubLayer_Radio(label="Bottom", fellow=rad, x=pw/2, y=3*LINE_EMS, w=pw/4, h=LINE_EMS, pos=LISTFIG_POSITION_BELOW, 352 color=COLOR_LF_OPTION, fill_color=COLOR_LF_OPTION, 353 active=(self.stimulus_position == LISTFIG_POSITION_BELOW))) 354 self.subStimulusPositionHidden = panel.add_sublayer(SubLayer_Radio(label="None", fellow=rad, x=3*pw/4, y=3*LINE_EMS, w=pw/4, h=LINE_EMS, pos=LISTFIG_POSITION_HIDDEN, 355 color=COLOR_LF_OPTION, fill_color=COLOR_LF_OPTION, 356 active=(self.stimulus_position == LISTFIG_POSITION_HIDDEN))) 357 self.subsStimulusPosition = [self.subStimulusPositionHidden, self.subStimulusPositionAbove, self.subStimulusPositionBelow] 358 panel.add_sublayer(SubLayer_Label('Draw signals:', -1, 1, x=1, y=4*LINE_EMS, w=pw/2, h=LINE_EMS)) 359 self.subStimulusSignals = panel.add_sublayer(SubLayer_Label("", -1, 1, x=1+pw/2, y=4*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 360 tooltip="click to pick which stimulus signals are drawn", 361 action=self.__ref(self.__onClickStimulusSignals))) 362 panel.add_sublayer(SubLayer_Label('Index of example:', -1, 1, x=1, y=5*LINE_EMS, w=pw/2, h=LINE_EMS)) 363 self.subStimulusTrace = panel.add_sublayer(SubLayer_Label('%i'%self.stimulus_trace, -1, 1, x=1+pw/2, y=5*LINE_EMS, w=pw/2, h=LINE_EMS, color=COLOR_LF_OPTION, 364 tooltip="Index of the trace (in the List table) whose stimulus is drawn", 365 action=self.__ref(bind(self.__onClickEditProperty, 'stimulus_trace', 'Index of example trace:', acceptIntGreaterThan(-1))), 366 scroll=self.__ref(bind_with_args(self.__onScrollIntProperty, 'stimulus_trace', 0, None)))) 367 self.subStimulusSampled = SubLayer_Check(caption="Draw sampled data", tooltip="enables drawing sampled data points", 368 active=self.stimulus_sampled, color=COLOR_LF_OPTION, 369 x=1, y=6*LINE_EMS, w=pw, h=LINE_EMS) 370 self.subStimulusSampled.OnToggle += self.__ref(bind(self.__onToggleProperty, 'stimulus_sampled')) 371 panel.add_sublayer(self.subStimulusSampled) 372 self.subStimulusIdealized = SubLayer_Check(caption="Draw idealized data", tooltip="enables drawing idealized events", 373 active=self.stimulus_idealized, color=COLOR_LF_OPTION, 374 x=1, y=7*LINE_EMS, w=pw, h=LINE_EMS) 375 self.subStimulusIdealized.OnToggle += self.__ref(bind(self.__onToggleProperty, 'stimulus_idealized')) 376 panel.add_sublayer(self.subStimulusIdealized) 377 self.subStimulusZeroLine = SubLayer_Check(caption="Draw zero line (baseline)", tooltip="draws a line at y=0", 378 active=self.stimulus_zero_line, color=COLOR_LF_OPTION, 379 x=1, y=8*LINE_EMS, w=pw, h=LINE_EMS) 380 self.subStimulusZeroLine.OnToggle += self.__ref(bind(self.__onToggleProperty, 'stimulus_zero_line')) 381 panel.add_sublayer(self.subStimulusZeroLine) 382 panel.add_sublayer(SubLayer_Label('Close', 0, 1, x=pw/2, y=10*LINE_EMS, w=10, h=1.2*LINE_EMS, cBG=LAYER_BG, border=1, 383 action=self.__ref(bind(self.__onClickHeader, None)))) 384 385 386 # Zoom: 387 panel = self.panZoom = qubx.toolspace.Layer(x=self.layHeaders.rq_x, y=self.layHeaders.rq_y+self.layHeaders.rq_h, w=self.layHeaders.rq_w, h=(0+4)*LINE_EMS) 388 self.panels.append(qubx.toolspace.LayerSet([panel])) 389 self.subZoomClose = panel.add_sublayer(SubLayer_Label('Close', 0, 1, x=pw/2, y=2*LINE_EMS, w=10, h=1.2*LINE_EMS, cBG=LAYER_BG, border=1, 390 action=self.__ref(bind(self.__onClickHeader, None)))) 391 392 393 # Scale Bars: 394 panel = qubx.toolspace.Layer(x=self.layHeaders.rq_x, y=self.layHeaders.rq_y+self.layHeaders.rq_h, w=self.layHeaders.rq_w, h=(9+4)*LINE_EMS) 395 self.panels.append(qubx.toolspace.LayerSet([panel])) 396 panel.add_sublayer(SubLayer_Label('Number format:', -1, 1, x=1, y=LINE_EMS, w=pw/4, h=LINE_EMS)) 397 self.subScalebarNumberFormat = panel.add_sublayer(SubLayer_Label(self.scalebar_number_format, -1, 1, x=1+pw/5, y=LINE_EMS, w=pw/5, h=LINE_EMS, color=COLOR_LF_OPTION, 398 tooltip="e.g. %.3g, look up 'printf' for examples", 399 action=self.__ref(bind(self.__onClickEditProperty, 'scalebar_number_format', 'Number format:')))) 400 panel.add_sublayer(SubLayer_Label('Font height:', -1, 1, x=1+(2*pw)/5, y=LINE_EMS, w=pw/5, h=LINE_EMS)) 401 self.subScalebarFontHeight = panel.add_sublayer(SubLayer_Label('%.2f'%self.scalebar_font_height, -1, 1, x=1+(3*pw)/5, y=LINE_EMS, w=pw/5, h=LINE_EMS, color=COLOR_LF_OPTION, 402 tooltip="as a proportion of trace height", 403 action=self.__ref(bind(self.__onClickEditProperty, 'scalebar_font_height', 'Scale bar font height:', acceptFloatBetween(0.0, 1.0))), 404 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'scalebar_font_height', 0.0, 1.0)))) 405 self.subScalebarVerticalLabels = SubLayer_Check(caption="Vertical labels", tooltip="writes text along the scale bar's axis, instead of horizontally", 406 active=self.scalebar_vertical_labels, color=COLOR_LF_OPTION, 407 x=1+(4*pw)/5, y=LINE_EMS, w=pw/5, h=LINE_EMS) 408 self.subScalebarVerticalLabels.OnToggle += self.__ref(bind(self.__onToggleProperty, 'scalebar_vertical_labels')) 409 panel.add_sublayer(SubLayer_Label('Scale bar position:', -1, 1, x=1, y=2*LINE_EMS, w=pw/6, h=LINE_EMS)) 410 rad = self.subScalebarPositionAbove = SubLayer_Radio(label="Top", x=(2*pw)/6, y=2*LINE_EMS, w=pw/6, h=LINE_EMS, 411 active=(self.scalebar_position == LISTFIG_POSITION_ABOVE), 412 color=COLOR_LF_OPTION, fill_color=COLOR_LF_OPTION, 413 on_radio=self.__ref(self.__onRadioScalebarPosition), pos=LISTFIG_POSITION_ABOVE) 414 panel.add_sublayer(rad) 415 self.subScalebarPositionBelow = panel.add_sublayer(SubLayer_Radio(label="Bottom", fellow=rad, x=(3*pw)/6, y=2*LINE_EMS, w=pw/6, h=LINE_EMS, pos=LISTFIG_POSITION_BELOW, 416 color=COLOR_LF_OPTION, fill_color=COLOR_LF_OPTION, 417 active=(self.scalebar_position == LISTFIG_POSITION_BELOW))) 418 self.subScalebarPositionRight = panel.add_sublayer(SubLayer_Radio(label="Right", fellow=rad, x=(4*pw)/6, y=2*LINE_EMS, w=pw/6, h=LINE_EMS, pos=LISTFIG_POSITION_RIGHT, 419 color=COLOR_LF_OPTION, fill_color=COLOR_LF_OPTION, 420 active=(self.scalebar_position == LISTFIG_POSITION_RIGHT))) 421 self.subScalebarPositionFloat = panel.add_sublayer(SubLayer_Radio(label="Float", fellow=rad, x=(5*pw)/6, y=2*LINE_EMS, w=pw/6, h=LINE_EMS, pos=LISTFIG_POSITION_FLOAT, 422 tooltip='you position each scale bar independently, below', 423 color=COLOR_LF_OPTION, fill_color=COLOR_LF_OPTION, 424 active=(self.scalebar_position == LISTFIG_POSITION_FLOAT))) 425 self.subScalebarPositionHidden = panel.add_sublayer(SubLayer_Radio(label="None", fellow=rad, x=pw/6, y=2*LINE_EMS, w=pw/6, h=LINE_EMS, pos=LISTFIG_POSITION_HIDDEN, 426 active=(self.scalebar_position == LISTFIG_POSITION_HIDDEN), 427 color=COLOR_LF_OPTION, fill_color=COLOR_LF_OPTION, 428 tooltip="Use the Notebook to generate scale bars separately, then place them where you choose on the figure.")) 429 self.subsScalebarPosition = [self.subScalebarPositionHidden, self.subScalebarPositionAbove, self.subScalebarPositionBelow, self.subScalebarPositionRight, self.subScalebarPositionFloat] 430 panel.add_sublayer(self.subScalebarVerticalLabels) 431 self.subScalebarTimebar = SubLayer_Check(caption="Time bar", tooltip="draws a separate scale bar for the time axis", 432 active=self.scalebar_timebar, color=COLOR_LF_OPTION, 433 x=1, y=3*LINE_EMS, w=10, h=LINE_EMS) 434 self.subScalebarTimebar.OnToggle += self.__ref(bind(self.__onToggleProperty, 'scalebar_timebar')) 435 panel.add_sublayer(self.subScalebarTimebar) 436 panel.add_sublayer(SubLayer_Label('at x:', 1, 1, x=13, y=3*LINE_EMS, w=4, h=LINE_EMS)) 437 self.subScalebarTimebarX = panel.add_sublayer(SubLayer_Label('%.3f'%self.scalebar_timebar_x, -1, 1, x=18, y=3*LINE_EMS, w=8, h=LINE_EMS, color=COLOR_LF_OPTION, 438 tooltip="as a proportion of figure width", 439 action=self.__ref(bind(self.__onClickEditProperty, 'scalebar_timebar_x', 'Time bar X position:', acceptFloatBetween(0.0, 1.0))), 440 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'scalebar_timebar_x', 0.0, 1.0)))) 441 panel.add_sublayer(SubLayer_Label('y:', 1, 1, x=27, y=3*LINE_EMS, w=4, h=LINE_EMS)) 442 self.subScalebarTimebarY = panel.add_sublayer(SubLayer_Label('%.3f'%self.scalebar_timebar_y, -1, 1, x=32, y=3*LINE_EMS, w=8, h=LINE_EMS, color=COLOR_LF_OPTION, 443 tooltip="as a proportion of figure height", 444 action=self.__ref(bind(self.__onClickEditProperty, 'scalebar_timebar_y', 'Time bar Y position:', acceptFloatBetween(0.0, 1.0))), 445 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'scalebar_timebar_y', 0.0, 1.0)))) 446 panel.add_sublayer(SubLayer_Label('Time bar width:', -1, 1, x=1, y=4*LINE_EMS, w=12, h=LINE_EMS)) 447 self.subScalebarTimebarAutodim = panel.add_sublayer(SubLayer_Check(caption="Auto", tooltip="picks a round number small enough to fit within the height of one trace", 448 active=self.scalebar_timebar_autodim, color=COLOR_LF_OPTION, 449 x=13, y=4*LINE_EMS, w=8, h=LINE_EMS)) 450 self.subScalebarTimebarAutodim.OnToggle += self.__ref(bind(self.__onToggleProperty, 'scalebar_timebar_autodim')) 451 panel.add_sublayer(SubLayer_Label('or', 1, 1, x=22, y=4*LINE_EMS, w=4, h=LINE_EMS)) 452 self.subScalebarTimebarDim = panel.add_sublayer(SubLayer_Label('%.3g'%self.scalebar_timebar_dim, -1, 1, x=27, y=4*LINE_EMS, w=8, h=LINE_EMS, color=COLOR_LF_OPTION, 453 tooltip="width of the time bar, in seconds", 454 action=self.__ref(bind(self.__onClickEditProperty, 'scalebar_timebar_dim', 'Time bar width (seconds):', acceptFloatGreaterThan(0.0))), 455 scroll=self.__ref(bind_with_args(self.__onScrollFloatProperty, 'scalebar_timebar_dim', 0.0, None)))) 456 panel.add_sublayer(SubLayer_Label('sec', -1, 1, x=36, y=4*LINE_EMS, w=2, h=LINE_EMS)) 457 458 panel.add_sublayer(SubLayer_Label('Signal:', -1, 1, x=1, y=6*LINE_EMS, w=8, h=LINE_EMS)) 459 self.subScalebarSignal = panel.add_sublayer(SubLayer_Label('foo', 0, 1, x=7, y=6*LINE_EMS, w=16, h=LINE_EMS, border=1, color=COLOR_LF_OPTION, 460 tooltip='click to pick a signal and show its options', 461 action=self.__ref(self.__onClickScalebarSignal))) 462 self.subScalebarSignalPrev = panel.add_sublayer(SubLayer_Label('<', 0, 1, x=26, y=6*LINE_EMS, w=LINE_EMS, h=LINE_EMS, border=1, 463 tooltip="show options for previous signal", 464 action=self.__ref(bind(lambda: (self.__scalebar_signal is None) or self.set_scalebar_signal(max(0, self.__scalebar_signal-1)))))) 465 self.subScalebarSignalNext = panel.add_sublayer(SubLayer_Label('>', 0, 1, x=28, y=6*LINE_EMS, w=LINE_EMS, h=LINE_EMS, border=1, 466 tooltip="show options for next signal", 467 action=self.__ref(bind(lambda: (self.__scalebar_signal is None) or self.set_scalebar_signal(min(len(self.__signals)-1, self.__scalebar_signal+1)))))) 468 self.subSsDrawScalebar = panel.add_sublayer(SubLayer_Check(caption='Draw scale bar', tooltip="", color=COLOR_LF_OPTION, 469 x=7, y=7*LINE_EMS, w=pw/5, h=LINE_EMS)) 470 self.subSsDrawTimebar = panel.add_sublayer(SubLayer_Check(caption='Draw time bar', tooltip="otherwise, draws only a vertical line", color=COLOR_LF_OPTION, 471 x=7+pw/5, y=7*LINE_EMS, w=pw/5, h=LINE_EMS)) 472 self.subSsInvertX = panel.add_sublayer(SubLayer_Check(caption='Invert X', tooltip="draws the time bar leftward from the origin", color=COLOR_LF_OPTION, 473 x=7+(2*pw)/5, y=7*LINE_EMS, w=pw/5, h=LINE_EMS)) 474 self.subSsInvertY = panel.add_sublayer(SubLayer_Check(caption='Invert Y', tooltip="draws the scale bar downward from the origin", color=COLOR_LF_OPTION, 475 x=7+(3*pw)/5, y=7*LINE_EMS, w=pw/5, h=LINE_EMS)) 476 panel.add_sublayer(SubLayer_Label('Height:', -1, 1, x=7, y=8*LINE_EMS, w=8, h=LINE_EMS)) 477 self.subSsAutodim = panel.add_sublayer(SubLayer_Check(caption='Auto', tooltip="picks a round number small enough to fit within the height of one trace", color=COLOR_LF_OPTION, 478 x=16, y=8*LINE_EMS, w=8, h=LINE_EMS)) 479 panel.add_sublayer(SubLayer_Label('or', 0, 1, x=25, y=8*LINE_EMS, w=2, h=LINE_EMS)) 480 self.subSsDim = panel.add_sublayer(SubLayer_Label('x', -1, 1, x=28, y=8*LINE_EMS, w=12, h=LINE_EMS, color=COLOR_LF_OPTION, tooltip='Height of the scale bar, in data units')) 481 panel.add_sublayer(SubLayer_Label('[pos:right]', -1, 1, x=7, y=9*LINE_EMS, w=8, h=LINE_EMS)) 482 self.subSsEveryTrace = panel.add_sublayer(SubLayer_Check(caption='in every trace', tooltip="otherwise, draws this scale bar only in the first relevant trace", color=COLOR_LF_OPTION, 483 x=15, y=9*LINE_EMS, w=8, h=LINE_EMS)) 484 panel.add_sublayer(SubLayer_Label('[pos:float] X:', 1, 1, x=30, y=9*LINE_EMS, w=10, h=LINE_EMS)) 485 self.subSsX = panel.add_sublayer(SubLayer_Label('x', -1, 1, x=41, y=9*LINE_EMS, w=8, h=LINE_EMS, color=COLOR_LF_OPTION, tooltip='horizontal position, as a proportion of figure width')) 486 panel.add_sublayer(SubLayer_Label('Y:', 1, 1, x=50, y=9*LINE_EMS, w=2, h=LINE_EMS)) 487 self.subSsY = panel.add_sublayer(SubLayer_Label('x', -1, 1, x=53, y=9*LINE_EMS, w=8, h=LINE_EMS, color=COLOR_LF_OPTION, tooltip='vertical position, as a proportion of figure height')) 488 489 panel.add_sublayer(SubLayer_Label('Close', 0, 1, x=pw/2, y=11*LINE_EMS, w=10, h=1.2*LINE_EMS, cBG=LAYER_BG, border=1, 490 action=self.__ref(bind(self.__onClickHeader, None)))) 491 492 # ? 493 panel = qubx.toolspace.Layer(x=self.layHeaders.rq_x, y=self.layHeaders.rq_y+self.layHeaders.rq_h, w=self.layHeaders.rq_w, h=(5+4)*LINE_EMS) 494 self.panels.append(qubx.toolspace.LayerSet([panel])) 495 panel.add_sublayer(SubLayer_Label("This panel generates publication figures from the data in a List.", 496 -1, 1, x=1, y=LINE_EMS, w=pw, h=LINE_EMS)) 497 panel.add_sublayer(SubLayer_Label("To use: build a List of selections in the Data panel, edit their Labels in the List table, and edit signal Name and Units in the Scope table.", 498 -1, 1, x=1, y=2*LINE_EMS, w=pw, h=LINE_EMS)) 499 panel.add_sublayer(SubLayer_Label("Click the headers (Layout, Draw, ...) for options.", 500 -1, 1, x=1, y=3*LINE_EMS, w=pw, h=LINE_EMS)) 501 panel.add_sublayer(SubLayer_Label("Set the output dimensions under Layout, then click the Notebook icon (at lower left) to copy or save the image.", 502 -1, 1, x=1, y=5*LINE_EMS, w=pw, h=LINE_EMS)) 503 panel.add_sublayer(SubLayer_Label('Close', 0, 1, x=pw/2, y=7*LINE_EMS, w=10, h=1.2*LINE_EMS, cBG=LAYER_BG, border=1, 504 action=self.__ref(bind(self.__onClickHeader, None)))) 505 506 507 ww = self.layHeaders.rq_x + self.layHeaders.rq_w - 4 - LINE_EMS 508 self.layWarnings = self.plot.add_layer(qubx.toolspace.Layer(x=4, y=-2*LINE_EMS, w=ww+LINE_EMS, h=LINE_EMS, cBG=COLOR_CLEAR)) 509 self.subWarnings = self.layWarnings.add_sublayer(SubLayer_Label("", 0, 1, w=-LINE_EMS, h=LINE_EMS, color=COLOR_LF_FG_TROUBLE, hover_color=COLOR_LF_BG)) 510 511 QubX = qubx.global_namespace.QubX 512 self.__datafile = self.__dataview = None 513 self.__signals = [] 514 QubX.Data.OnSwitch += self.__ref(self.__onSwitchData) 515 QubX.Data.table.OnSet += self.__ref(self.__onSetDataTable) 516 517 self.layNotebook = qubx.toolspace.Layer(x=1, y=-3, w=2, h=2, cBG=COLOR_CLEAR) 518 self.subNotebook = qubx.notebookGTK.SubLayer_Notebook(x=0, y=0, w=2, h=2) 519 self.layNotebook.add_sublayer(self.subNotebook) 520 self.plot.add_layer(self.layNotebook) 521 self.nbPicture = qubx.notebookGTK.NbPicture('Picture', self.global_name and ('%s.nbPicture'%self.global_name) or '', 522 self.nb_picture_get_shape, 523 self.nb_picture_draw) 524 self.nbScalebars = qubx.notebookGTK.NbPicture('Scale Bars', self.global_name and ('%s.nbScalebars'%self.global_name) or '', 525 self.nb_scalebars_get_shape, 526 self.nb_scalebars_draw) 527 self.subNotebook.items.append(self.nbPicture) 528 self.subNotebook.items.append(self.nbScalebars) 529 530 self.__onSwitchData(QubX.Data, QubX.Data.file) 531 self.__onClickHeader(6) # show info
532 scalebar_signal = property(lambda self: self.__scalebar_signal, lambda self, x: self.set_scalebar_signal(x), 'Index of signal whose scale bars are being edited')
533 - def set_scalebar_signal(self, x):
534 ref = self.__scalebar_signal_ref = Reffer() # clears stale event handlers 535 self.__scalebar_signal = x 536 if not (0 <= x < len(self.__signals)): 537 x = None 538 if x is None: 539 self.subScalebarSignal.label = '<none>' 540 else: 541 rec = self.__signals[x] 542 self.subScalebarSignal.label = rec.Name 543 self.subSsDrawScalebar.active = bool(getattr(self, "%s_in_scalebar"%rec.Name)) 544 self.subSsDrawTimebar.active = bool(getattr(self, "%s_sb_timebar"%rec.Name)) 545 self.subSsInvertX.active = bool(getattr(self, "%s_sb_invert_x"%rec.Name)) 546 self.subSsInvertY.active = bool(getattr(self, "%s_sb_invert_y"%rec.Name)) 547 self.subSsAutodim.active = bool(getattr(self, "%s_sb_autodim"%rec.Name)) 548 self.subSsDim.label = '%.3g' % getattr(self, "%s_sb_dim"%rec.Name) 549 self.subSsEveryTrace.active = bool(getattr(self, "%s_sb_every_trace"%rec.Name)) 550 self.subSsX.label = '%.3f' % getattr(self, "%s_sb_float_x"%rec.Name) 551 self.subSsY.label = '%.3f' % getattr(self, "%s_sb_float_y"%rec.Name) 552 self.subSsDrawScalebar.OnToggle += ref(bind(self.__onToggleProperty, '%s_in_scalebar'%rec.Name)) 553 self.subSsDrawTimebar.OnToggle += ref(bind(self.__onToggleProperty, '%s_sb_timebar'%rec.Name)) 554 self.subSsInvertX.OnToggle += ref(bind(self.__onToggleProperty, '%s_sb_invert_x'%rec.Name)) 555 self.subSsInvertY.OnToggle += ref(bind(self.__onToggleProperty, '%s_sb_invert_y'%rec.Name)) 556 self.subSsAutodim.OnToggle += ref(bind(self.__onToggleProperty, '%s_sb_autodim'%rec.Name)) 557 self.subSsDim.action = ref(bind(self.__onClickEditProperty, '%s_sb_dim'%rec.Name, 'Scale bar width:', acceptFloatGreaterThan(0.0))) 558 self.subSsDim.scroll = ref(bind_with_args(self.__onScrollFloatProperty, '%s_sb_dim'%rec.Name, 0.0, None)) 559 self.subSsEveryTrace.OnToggle += ref(bind(self.__onToggleProperty, '%s_sb_every_trace'%rec.Name)) 560 self.subSsX.action = ref(bind(self.__onClickEditProperty, '%s_sb_float_x'%rec.Name, 'Scale bar X position:', acceptFloatBetween(0.0, 1.0))) 561 self.subSsX.scroll = ref(bind_with_args(self.__onScrollFloatProperty, '%s_sb_float_x'%rec.Name, 0.0, 1.0)) 562 self.subSsY.action = ref(bind(self.__onClickEditProperty, '%s_sb_float_y'%rec.Name, 'Scale bar Y position:', acceptFloatBetween(0.0, 1.0))) 563 self.subSsY.scroll = ref(bind_with_args(self.__onScrollFloatProperty, '%s_sb_float_y'%rec.Name, 0.0, 1.0))
564 - def propertied_set(self, value, name):
565 super(ListFigureFace, self).propertied_set(value, name) 566 if name == 'output_w': 567 self.subOutputW.label = '%i' % value 568 elif name == 'output_h': 569 self.subOutputH.label = '%i' % value 570 elif name == 'margin_h': 571 self.subMarginH.label = '%.2f' % value 572 elif name == 'margin_v': 573 self.subMarginV.label = '%.2f' % value 574 elif name == 'tracepad_h': 575 self.subTracepadH.label = '%.2f' % value 576 elif name == 'tracepad_v': 577 self.subTracepadV.label = '%.2f' % value 578 elif name == 'font_face': 579 self.subFontFace.label = value 580 elif name == 'font_weight': 581 self.subFontWeight.label = '%i' % value 582 elif name == 'font_height': 583 self.subFontHeight.label = '%.2f' % value 584 elif name == 'font_max_width': 585 self.subFontMaxWidth.label = '%.2f' % value 586 elif name == 'draw_exact': 587 self.subDrawExact.active = bool(value) 588 elif name == 'dot_size': 589 self.subDotSize.label = '%.2f' % value 590 elif name == 'line_width': 591 self.subLineWidth.label = '%.2f' % value 592 elif name == 'line_opacity': 593 self.subLineOpacity.label = '%.2f' % value 594 elif name == 'zero_line_width': 595 self.subZeroLineWidth.label = '%.2f' % value 596 elif name == 'multi_file': 597 self.subMultiFile.active = bool(value) 598 elif name == 'multi_file_group': 599 self.subMultiFileGroup.label = '%i' % value 600 elif name == 'trace_label_format': 601 self.subTraceLabelFormat.label = value 602 elif name == 'trace_label_baseline': 603 self.subTraceLabelBaseline.label = '%.2f' % value 604 elif name == 'trace_sampled': 605 self.subTraceSampled.active = bool(value) 606 elif name == 'trace_idealized': 607 self.subTraceIdealized.active = bool(value) 608 elif name == 'trace_idealized_above': 609 self.subTraceIdealizedAbove.active = bool(value) 610 elif name == 'trace_fit': 611 self.subTraceFit.active = bool(value) 612 elif name == 'trace_zero_line': 613 self.subTraceZeroLine.active = bool(value) 614 elif name == 'stimulus_label_format': 615 self.subStimulusLabelFormat.label = value 616 elif name == 'stimulus_label_baseline': 617 self.subStimulusLabelBaseline.label = '%.2f' % value 618 elif name == 'stimulus_position': 619 self.subsStimulusPosition[value].active = True 620 elif name == 'stimulus_trace': 621 self.subStimulusTrace.label = '%i' % value 622 elif name == 'stimulus_sampled': 623 self.subStimulusSampled.active = bool(value) 624 elif name == 'stimulus_idealized': 625 self.subStimulusIdealized.active = bool(value) 626 elif name == 'stimulus_zero_line': 627 self.subStimulusZeroLine.active = bool(value) 628 elif name == 'scalebar_number_format': 629 self.subScalebarNumberFormat.label = value 630 elif name == 'scalebar_font_height': 631 self.subScalebarFontHeight.label = '%.2f' % value 632 elif name == 'scalebar_position': 633 self.subsScalebarPosition[value].active = True 634 elif name == 'scalebar_vertical_labels': 635 self.subScalebarVerticalLabels.active = bool(value) 636 elif name == 'scalebar_timebar': 637 self.subScalebarTimebar.active = bool(value) 638 elif name == 'scalebar_timebar_x': 639 self.subScalebarTimebarX.label = '%.3f' % value 640 elif name == 'scalebar_timebar_y': 641 self.subScalebarTimebarY.label = '%.3f' % value 642 elif name == 'scalebar_timebar_autodim': 643 self.subScalebarTimebarAutodim.active = bool(value) 644 elif name == 'scalebar_timebar_dim': 645 self.subScalebarTimebarDim.label = '%.3f' % value 646 else: 647 found = [None] # boxed for modification below 648 def is_signal_prop(pname): 649 match = re.match("(.*)_%s"%pname, name) 650 if match: 651 for rec in self.__signals: 652 if rec.Name == match.group(1): 653 found[0] = rec 654 return True 655 return False
656 if is_signal_prop('in_trace'): 657 self.__update_trace_signals() 658 elif is_signal_prop('in_stimulus'): 659 self.__update_stimulus_signals() 660 elif is_signal_prop('min'): 661 found[0].lbl_min.label = '%.3g' % value 662 elif is_signal_prop('max'): 663 found[0].lbl_max.label = '%.3g' % value 664 elif is_signal_prop('in_scalebar'): 665 if self.__signals.index(found[0]) == self.__scalebar_signal: 666 self.subSsDrawScalebar.active = bool(value) 667 elif is_signal_prop('sb_timebar'): 668 if self.__signals.index(found[0]) == self.__scalebar_signal: 669 self.subSsDrawTimebar.active = bool(value) 670 elif is_signal_prop('sb_invert_x'): 671 if self.__signals.index(found[0]) == self.__scalebar_signal: 672 self.subSsInvertX.active = bool(value) 673 elif is_signal_prop('sb_invert_y'): 674 if self.__signals.index(found[0]) == self.__scalebar_signal: 675 self.subSsInvertY.active = bool(value) 676 elif is_signal_prop('sb_autodim'): 677 if self.__signals.index(found[0]) == self.__scalebar_signal: 678 self.subSsAutodim.active = bool(value) 679 elif is_signal_prop('sb_dim'): 680 if self.__signals.index(found[0]) == self.__scalebar_signal: 681 self.subSsDim.label = '%.3g' % value 682 elif is_signal_prop('sb_every_trace'): 683 if self.__signals.index(found[0]) == self.__scalebar_signal: 684 self.subSsEveryTrace.active = bool(value) 685 elif is_signal_prop('sb_float_x'): 686 if self.__signals.index(found[0]) == self.__scalebar_signal: 687 self.subSsX.label = '%.3f' % value 688 elif is_signal_prop('sb_float_y'): 689 if self.__signals.index(found[0]) == self.__scalebar_signal: 690 self.subSsY.label = '%.3f' % value 691 if not (name in ('output_w', 'output_h')): 692 self.update()
693
694 - def __signals_to_string(self, tag):
695 return ";".join(rec.Name for rec in self.__signals if getattr(self, "%s%s" % (rec.Name, tag)))
696 - def __update_trace_signals(self):
697 self.subTraceSignals.label = self.__signals_to_string("_in_trace") 698 if self.subTraceSignals.label: 699 self.subTraceSignals.cBG = COLOR_CLEAR 700 elif self.__signals: 701 self.subTraceSignals.cBG = COLOR_LF_BG_TROUBLE 702 self.__onClickHeader(2)
703 - def __update_stimulus_signals(self):
704 self.subStimulusSignals.label = self.__signals_to_string("_in_stimulus")
705 - def __popup_signals(self, tag):
706 menu = gtk.Menu() 707 for rec in self.__signals: 708 active = getattr(self, "%s%s" % (rec.Name, tag)) 709 build_menuitem(rec.Name, rec.ref(bind(self.__onClickSignal, rec, tag, active)), 710 active=active, item_class=gtk.CheckMenuItem, menu=menu) 711 menu.popup(None, None, None, 0, 0)
712 - def __onClickSignal(self, rec, tag, active):
713 self.propertied_on_user_set("%s%s" % (rec.Name, tag), not active)
714 - def __onClickScalebarSignal(self, x, y, e):
715 ref = self.__ref_scalebar_signal = Reffer() 716 menu = gtk.Menu() 717 for i, rec in enumerate(self.__signals): 718 build_menuitem(rec.Name, ref(bind(self.set_scalebar_signal, i)), menu=menu, active=self.__scalebar_signal==i, item_class=gtk.CheckMenuItem) 719 menu.popup(None, None, None, 0, 0)
720
721 - def __onPresetsPopup(self):
722 self.subPresets.popup.foreach(lambda item: self.subPresets.popup.remove(item)) 723 self.subPresets.ref = Reffer() 724 for name in qubx.settings.SettingsMgr['ListFigure'].listNames(): 725 build_menuitem(name, self.subPresets.ref(bind(self.__use_preset, name)), menu=self.subPresets.popup) 726 build_menuitem('Manage...', self.__ref(self.__onClickManagePresets), menu=self.subPresets.popup) 727 build_menuitem('Add to menu...', self.__ref(self.__onClickAddPreset), menu=self.subPresets.popup) 728 return True
729 - def __use_preset(self, name):
730 qubx.pyenv.env.OnScriptable('SettingsMgr["ListFigure"].load(%s)' % repr(name)) 731 qubx.settings.SettingsMgr['ListFigure'].load(name)
732 - def __onClickManagePresets(self, item):
733 dlg = qubx.settingsGTK.PresetsDialog('ListFigure', self.parent_window) 734 dlg.run() 735 dlg.destroy()
736 - def __onClickAddPreset(self, item):
737 name = prompt_entry('Preset name:', '', str, '%s', 'ListFigure presets: Add to menu...', self.parent_window) 738 if not name: return 739 qubx.pyenv.env.OnScriptable('SettingsMgr["ListFigure"].save(%s)' % repr(name)) 740 qubx.settings.SettingsMgr['ListFigure'].save(name)
741
742 - def onShow(self, showing):
743 self.update()
744 - def __onSetDataTable(self, i, field, val, prev, undoing):
745 if (field == 'Group') and self.multi_file and (self.multi_file_group in (val, prev)): 746 self.update()
747 - def __onSwitchData(self, Data, file):
748 if self.__datafile: 749 self.__dataview.OnListSorted -= self.__ref(self.__onChangeList) 750 self.__dataview.signals.OnInsert -= self.__ref(self.__onInsertSignal) 751 self.__dataview.signals.OnRemoving -= self.__ref(self.__onRemovingSignal) 752 self.__dataview.signals.OnSet -= self.__ref(self.__onSetSignal) 753 for i in reversed(xrange(1, self.__dataview.signals.size)): 754 self.__onRemovingSignal(i, False) 755 self.__datafile.lists.OnChangeList -= self.__ref(self.__onChangeList) 756 self.__datafile.lists.OnSwitchList -= self.__ref(self.__onChangeList) 757 self.__datafile.OnChangeSamples -= self.__ref(self.__onSamplesChanged) 758 self.__datafile.OnChangeIdealization -= self.__ref(self.__onSamplesChanged) 759 self.__datafile.OnChangeFits -= self.__ref(self.__onSamplesChanged) 760 self.__datafile = file if (not (file.signals is None)) else None 761 if self.__datafile: 762 self.__dataview = Data.view 763 self.__dataview.OnListSorted += self.__ref(self.__onChangeList) 764 self.__datafile.lists.OnChangeList += self.__ref(self.__onChangeList) 765 self.__datafile.lists.OnSwitchList += self.__ref(self.__onChangeList) 766 for i in xrange(1, self.__dataview.signals.size): 767 self.__onInsertSignal(i, False) 768 self.__dataview.signals.OnInsert += self.__ref(self.__onInsertSignal) 769 self.__dataview.signals.OnRemoving += self.__ref(self.__onRemovingSignal) 770 self.__dataview.signals.OnSet += self.__ref(self.__onSetSignal) 771 # assuming signal names don't change 772 self.__datafile.OnChangeSamples += self.__ref(self.__onSamplesChanged) 773 self.__datafile.OnChangeIdealization += self.__ref(self.__onSamplesChanged) 774 self.__datafile.OnChangeFits += self.__ref(self.__onSamplesChanged) 775 else: 776 self.__dataview = None 777 self.scalebar_signal = self.__scalebar_signal 778 self.update()
779 - def __onChangeList(self, *args, **kw):
780 self.update() 781 if self.__datafile and self.__datafile.list and self.__datafile.list.size and ((self.stimulus_trace >= self.__datafile.list.size - 1)): 782 self.stimulus_trace = self.__datafile.list.size - 1
783 - def __setup_signal(self, rec, prev_name=None):
784 ix = rec.Index 785 if not hasattr(self, '%s_in_trace' % rec.Name): # original properties 786 if prev_name is None: # use defaults 787 lo, hi = self.__dataview.display_limits(ix) 788 self.propertied_add_property(Property('%s_in_trace' % rec.Name, (rec.Center != qubx.dataGTK.SIGNAL_CENTER_HIDDEN), "")) 789 self.propertied_add_property(Property('%s_in_stimulus' % rec.Name, 0, "")) 790 self.propertied_add_property(Property('%s_in_scalebar' % rec.Name, (rec.Center != qubx.dataGTK.SIGNAL_CENTER_HIDDEN), "")) 791 self.propertied_add_property(Property('%s_min' % rec.Name, lo, "")) 792 self.propertied_add_property(Property('%s_max' % rec.Name, hi, "")) 793 else: # use properties from previous signal name 794 self.propertied_add_property(Property('%s_in_trace' % rec.Name, getattr(self, '%s_in_trace'%prev_name), "")) 795 self.propertied_add_property(Property('%s_in_stimulus' % rec.Name, getattr(self, '%s_in_stimulus'%prev_name), "")) 796 self.propertied_add_property(Property('%s_in_scalebar' % rec.Name, getattr(self, '%s_in_scalebar'%prev_name), "")) 797 self.propertied_add_property(Property('%s_min' % rec.Name, getattr(self, '%s_min'%prev_name), "")) 798 self.propertied_add_property(Property('%s_max' % rec.Name, getattr(self, '%s_max'%prev_name), "")) 799 if not hasattr(self, '%s_scalebar_timebar' % rec.Name): # second-gen properties 800 if prev_name is None: 801 self.propertied_add_property(Property('%s_sb_timebar' % rec.Name, 1, "")) 802 self.propertied_add_property(Property('%s_sb_invert_x' % rec.Name, 1, "")) 803 self.propertied_add_property(Property('%s_sb_invert_y' % rec.Name, 0, "")) 804 self.propertied_add_property(Property('%s_sb_autodim' % rec.Name, 1, "")) 805 self.propertied_add_property(Property('%s_sb_dim' % rec.Name, 1.0, "")) 806 self.propertied_add_property(Property('%s_sb_every_trace' % rec.Name, 0, "")) 807 self.propertied_add_property(Property('%s_sb_float_x' % rec.Name, 0.9, "")) 808 self.propertied_add_property(Property('%s_sb_float_y' % rec.Name, 0.9, "")) 809 else: 810 self.propertied_add_property(Property('%s_sb_timebar' % rec.Name, getattr(self, '%s_sb_timebar'%prev_name), "")) 811 self.propertied_add_property(Property('%s_sb_invert_x' % rec.Name, getattr(self, '%s_sb_invert_x'%prev_name), "")) 812 self.propertied_add_property(Property('%s_sb_invert_y' % rec.Name, getattr(self, '%s_sb_invert_y'%prev_name), "")) 813 self.propertied_add_property(Property('%s_sb_autodim' % rec.Name, getattr(self, '%s_sb_autodim'%prev_name), "")) 814 self.propertied_add_property(Property('%s_sb_dim' % rec.Name, getattr(self, '%s_sb_dim'%prev_name), "")) 815 self.propertied_add_property(Property('%s_sb_every_trace' % rec.Name, getattr(self, '%s_sb_every_trace'%prev_name), "")) 816 self.propertied_add_property(Property('%s_sb_float_x' % rec.Name, getattr(self, '%s_sb_float_x'%prev_name), "")) 817 self.propertied_add_property(Property('%s_sb_float_y' % rec.Name, getattr(self, '%s_sb_float_y'%prev_name), "")) 818 pw = self.layHeaders.rq_w - 2 # margin 819 new_controls = False 820 if rec.caption_min: 821 rec.caption_min.label = '%s min:' % rec.Name 822 rec.lbl_min.label = '%.3g' % getattr(self, "%s_min"%rec.Name) 823 rec.caption_max.label = '%s max:' % rec.Name 824 rec.lbl_max.label = '%.3g' % getattr(self, "%s_max"%rec.Name) 825 else: 826 rec.caption_min = self.panZoom.add_sublayer(SubLayer_Label('%s min:' % rec.Name, -1, 1, x=1, y=ix*LINE_EMS, w=pw/4, h=LINE_EMS)) 827 rec.lbl_min = self.panZoom.add_sublayer(SubLayer_Label('%.3g' % getattr(self, "%s_min"%rec.Name), -1, 1, x=1+pw/4, y=ix*LINE_EMS, w=pw/4, h=LINE_EMS, color=COLOR_LF_OPTION)) 828 rec.caption_max = self.panZoom.add_sublayer(SubLayer_Label('%s max:' % rec.Name, -1, 1, x=1+pw/2, y=ix*LINE_EMS, w=pw/4, h=LINE_EMS)) 829 rec.lbl_max = self.panZoom.add_sublayer(SubLayer_Label('%.3g' % getattr(self, "%s_max"%rec.Name), -1, 1, x=1+3*pw/4, y=ix*LINE_EMS, w=pw/4-6-LINE_EMS, h=LINE_EMS, color=COLOR_LF_OPTION)) 830 rec.btn_invert = self.panZoom.add_sublayer(SubLayer_Label('Invert', 0, 1, x=-LINE_EMS-8, y=ix*LINE_EMS, w=7, h=LINE_EMS, border=1, 831 tooltip="Displays this signal upside-down", 832 action=rec.ref(bind(self.__onClickInvert, rec)))) 833 rec.btn_grab = self.panZoom.add_sublayer(SubLayer_Label('?', 0, 1, x=-LINE_EMS-1, y=ix*LINE_EMS, w=LINE_EMS, h=LINE_EMS, border=1, 834 tooltip="Updates %s min and max from the Data panel" % rec.Name, 835 action=rec.ref(bind(self.__onClickGrabZoom, rec)))) 836 new_controls = True 837 if prev_name or new_controls: 838 rec.lbl_min.action = rec.ref(bind(self.__onClickEditProperty, '%s_min'%rec.Name, '%s min:'%rec.Name, acceptFloat)) 839 rec.lbl_min.scroll = rec.ref(bind_with_args(self.__onScrollFloatProperty, '%s_min'%rec.Name, None, None)) 840 rec.lbl_max.action = rec.ref(bind(self.__onClickEditProperty, '%s_max'%rec.Name, '%s max:'%rec.Name, acceptFloat)) 841 rec.lbl_max.scroll = rec.ref(bind_with_args(self.__onScrollFloatProperty, '%s_max'%rec.Name, None, None))
842 - def __onInsertSignal(self, ix, undoing):
843 if ix == 0: return 844 rec = Anon(self.__dataview.signals[ix]) # unlike TableRow, Anon allows modification 845 rec.ref = Reffer() 846 self.__signals.insert(ix-1, rec) 847 self.__setup_signal(rec) 848 for rec in self.__signals[ix:]: 849 rec.Index += 1 850 for sub in (rec.caption_min, rec.lbl_min, rec.caption_max, rec.lbl_max, rec.btn_grab, rec.btn_invert): 851 sub.y = sub.rq_y + LINE_EMS 852 self.subZoomClose.y = self.subZoomClose.rq_y + LINE_EMS 853 self.panZoom.h = self.panZoom.rq_h + LINE_EMS 854 self.__update_trace_signals() 855 self.__update_stimulus_signals() 856 if self.__scalebar_signal is None: 857 self.scalebar_signal = 0
858 - def __onRemovingSignal(self, ix, undoing):
859 if ix == 0: return 860 rec = self.__signals[ix-1] 861 self.panZoom.remove_sublayer(rec.btn_invert) 862 self.panZoom.remove_sublayer(rec.btn_grab) 863 self.panZoom.remove_sublayer(rec.lbl_max) 864 self.panZoom.remove_sublayer(rec.caption_max) 865 self.panZoom.remove_sublayer(rec.lbl_min) 866 self.panZoom.remove_sublayer(rec.caption_min) 867 del self.__signals[ix-1] 868 if ix < (len(self.__signals) - 1): 869 for rec in self.__signals[ix-1:]: 870 rec.Index -= 1 871 for sub in (rec.caption_min, rec.lbl_min, rec.caption_max, rec.lbl_max, rec.btn_grab, rec.btn_invert): 872 sub.y = sub.rq_y - LINE_EMS 873 self.subZoomClose.y = self.subZoomClose.rq_y - LINE_EMS 874 self.panZoom.h = self.panZoom.rq_h - LINE_EMS 875 self.__update_trace_signals() 876 self.__update_stimulus_signals() 877 if self.__scalebar_signal == ix: 878 self.scalebar_signal = self.__scalebar_signal
879 - def __onSetSignal(self, ix, field, val, prev, undoing):
880 setattr(self.__signals[ix-1], field, val) 881 if field == 'Name': 882 self.__setup_signal(self.__signals[ix-1], prev) 883 self.__update_trace_signals() 884 self.__update_stimulus_signals() 885 if self.__scalebar_signal == (ix-1): 886 self.scalebar_signal = self.__scalebar_signal 887 self.update()
888 - def __onSamplesChanged(self, *args):
889 self.update()
890 - def update(self):
891 if self.showing: 892 self.plot.redraw_canvas(True)
893
894 - def __onClickHeader(self, index=None):
895 self.plot.layerset = None if (index is None) else self.panels[index] 896 if not (self.__last_panel is None): 897 self.headers[self.__last_panel].cBG = COLOR_LF_TAB 898 self.__last_panel = None 899 if not (index is None): 900 self.headers[index].cBG = COLOR_LF_TAB_SEL 901 self.__last_panel = index
902
903 - def __onClickEditProperty(self, name, label, accept=str):
904 val = prompt_entry(label, getattr(self, name), accept, parent=self.parent_window) 905 if not (val is None): 906 self.propertied_on_user_set(name, val)
907 - def __onScrollIntProperty(self, name, low=None, high=None, x=0, y=0, event=None, offset=0):
908 self.propertied_on_user_set(name, scrolled_int(getattr(self, name), offset, event.state&gdk.CONTROL_MASK, event.state&gdk.SHIFT_MASK, low, high))
909 - def __onScrollFloatProperty(self, name, low=None, high=None, x=0, y=0, event=None, offset=0):
910 self.propertied_on_user_set(name, scrolled_float(getattr(self, name), offset, event.state&gdk.CONTROL_MASK, event.state&gdk.SHIFT_MASK, low, high))
911 - def __onToggleProperty(self, name):
912 self.propertied_on_user_set(name, not getattr(self, name))
913
914 - def __onClickFontFace(self, x, y, e):
915 dlg = gtk.FontSelectionDialog("Pick a font...") 916 dlg.get_font_selection().set_preview_text('Caption') 917 dlg.set_font_name('%s%s %i' % (self.font_face, self.plot.appearance.font_bold and " bold" or "", self.plot.appearance.font_size)) 918 fn = None 919 if gtk.RESPONSE_OK == dlg.run(): 920 fn = dlg.get_font_selection().get_family().get_name() 921 dlg.destroy() 922 if not (fn is None): 923 self.propertied_on_user_set('font_face', fn)
924 - def __onClickTraceSignals(self, x, y, e):
925 self.__popup_signals('_in_trace')
926 - def __onClickStimulusSignals(self, x, y, e):
927 self.__popup_signals('_in_stimulus')
928 - def __onRadioStimulusPosition(self, sub):
929 self.propertied_on_user_set('stimulus_position', self.subsStimulusPosition.index(sub))
930 - def __onClickGrabZoom(self, rec):
931 lo, hi = self.__dataview.display_limits(rec.Index) 932 self.propertied_on_user_set('%s_min' % rec.Name, lo) 933 self.propertied_on_user_set('%s_max' % rec.Name, hi)
934 - def __onClickInvert(self, rec):
935 hi = getattr(self, "%s_min"%rec.Name) 936 setattr(self, "%s_min"%rec.Name, getattr(self, "%s_max"%rec.Name)) 937 setattr(self, "%s_max"%rec.Name, hi)
938 - def __onClickScalebarSignals(self, x, y, e):
939 self.__popup_signals('_in_scalebar')
940 - def __onRadioScalebarPosition(self, sub):
941 self.propertied_on_user_set('scalebar_position', self.subsScalebarPosition.index(sub))
942
943 - def nb_picture_get_shape(self):
944 return (self.output_w, self.output_h)
945 - def nb_picture_draw(self, context, w, h):
946 self.plot.draw_to_context(context, w, h, overlay=False)
947
948 - def nb_scalebars_get_shape(self):
949 g, di = self.prepare_draw(self.output_w, self.output_h) 950 w = g.tracepad_h 951 for i, rec in enumerate(reversed(di.scalebar_signals)): 952 one_w, one_h = measure_scalebar(g, di, rec, do_xlabel=(i==0)) 953 w += one_w 954 return int(round(w)), int(round(one_h+2*di.xlabel_m.height))
955 - def nb_scalebars_draw(self, context, w, h):
956 context.set_source_rgba(1, 1, 1, 1) 957 context.paint() 958 g, di = self.prepare_draw(self.output_w, self.output_h, context) 959 draw_scalebars(context, g, di, -g.tracepad_h, di.xlabel_m.height-g.tracepad_v, w)
960
961 - def prepare_draw(self, w, h, context=None, view=None):
962 # sets up font, line width; returns geometry, datainfo 963 964 if context is None: 965 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 128, 128) 966 cr = cairo.Context(surface) 967 else: 968 cr = context 969 970 if (view is None) and self.multi_file: 971 Data = qubx.global_namespace.QubX.Data 972 views = [Data.views[i] for i in xrange(len(Data.views)) if (Data.table[i, 'Group'] == self.multi_file_group)] 973 n = len(views) or 1 974 r, c = qubx.GTK.aspect_dimensions(w or 1, h or 1, n) 975 w1, h1 = w/c, h/r 976 g = Anon(sub=[]) 977 di = Anon(sub=[]) 978 i = j = 0 979 for v in views: 980 g1, di1 = self.prepare_draw(w1, h1, cr, v) 981 if g1 is None: 982 continue 983 g1.xoff, g1.yoff = i*w1, j*h1 984 g.sub.append(g1) 985 di.sub.append(di1) 986 i += 1 987 if i == c: 988 i = 0 989 j += 1 990 # the front view is the main g,di pair that's returned, also in the .sub lists 991 if v == self.__dataview: 992 g1.sub = g.sub 993 di1.sub = di.sub 994 g, di = g1, di1 995 g.multi_file = True 996 return g, di 997 998 dataview = self.__dataview if view is None else view 999 cr.select_font_face(self.font_face, cairo.FONT_SLANT_NORMAL, 1000 (self.font_weight >= 750) and cairo.FONT_WEIGHT_BOLD or cairo.FONT_WEIGHT_NORMAL) 1001 1002 g = Anon( # geometry 1003 line_width_input=self.line_width, line_opacity=self.line_opacity, zero_line_width_input=self.zero_line_width, 1004 trace_sampled=self.trace_sampled, trace_fit=self.trace_fit, trace_zero_line=self.trace_zero_line, 1005 trace_idealized=self.trace_idealized, trace_idealized_above=self.trace_idealized_above, 1006 trace_label_baseline=self.trace_label_baseline, 1007 stimulus_sampled=self.stimulus_sampled, stimulus_idealized=self.stimulus_idealized, stimulus_zero_line=self.stimulus_zero_line, 1008 stimulus_label_baseline=self.stimulus_label_baseline, stimulus_trace=self.stimulus_trace) 1009 1010 1011 di = Anon( # data info 1012 signal_segs={}, trace_signals=[], stimulus_signals=[], scalebar_signals=[], 1013 warnings=[], scalebar_format_fail=False) 1014 1015 signals = self.__signals if (dataview == self.__dataview) else [Anon(dataview.signals[i]) for i in xrange(1, dataview.signals.size)] 1016 for rec in signals: 1017 used = False 1018 scaled = False 1019 if getattr(self, "%s_in_trace" % rec.Name): 1020 di.trace_signals.append(rec) 1021 used = True 1022 if getattr(self, "%s_in_stimulus" % rec.Name): 1023 di.stimulus_signals.append(rec) 1024 used = True 1025 if getattr(self, "%s_in_scalebar" % rec.Name): 1026 di.scalebar_signals.append(rec) 1027 scaled = True 1028 # don't need data series (used = False) 1029 if used: 1030 di.signal_segs[rec.Name] = dataview.get_segmentation_list(signal=dataview.signals.index(rec.Name)-1) 1031 if used or scaled: 1032 rec.lo, rec.hi = getattr(self, "%s_min"%rec.Name), getattr(self, "%s_max"%rec.Name) 1033 if not di.trace_signals: 1034 return None, None 1035 1036 di.Nseg = 0 1037 di.maxNsample = 0 1038 if di.signal_segs: 1039 di.Nseg = len(di.signal_segs[di.trace_signals[0].Name]) 1040 for seg in di.signal_segs[di.trace_signals[0].Name]: 1041 di.maxNsample = max(di.maxNsample, seg.n) 1042 if not di.maxNsample: 1043 return None, None 1044 listview = qubx.global_namespace.QubX.Tables.find_view('List') 1045 if listview.table == dataview.file.list: 1046 # sorted order only defined for onscreen data file 1047 di.seg_order = [listview.raw_ix(i) for i in xrange(di.Nseg)] 1048 else: 1049 di.seg_order = range(di.Nseg) 1050 1051 g.margin_h = int(round(self.margin_h * w)) 1052 g.margin_v = int(round(self.margin_v * h)) 1053 g.cw = w - 2*g.margin_h 1054 g.ch = h - 2*g.margin_v 1055 1056 Ntrace = ((self.trace_idealized_above and 2 or 1) * len(di.trace_signals) * di.Nseg 1057 + (self.stimulus_position != LISTFIG_POSITION_HIDDEN) * len(di.stimulus_signals) 1058 + ((self.scalebar_position in (LISTFIG_POSITION_ABOVE, LISTFIG_POSITION_BELOW)) and 1 or 0)) or 1 1059 g.tracepad_h = int(round(self.tracepad_h * g.cw)) 1060 g.tracepad_v = int(round(self.tracepad_v * g.ch / Ntrace)) 1061 g.trace_h = int(round((g.ch*1.0/Ntrace)*(1.0 - 2*self.tracepad_v))) 1062 g.line_h = (g.trace_h+2*g.tracepad_v) * (self.trace_idealized_above and 2 or 1) * len(di.trace_signals) 1063 g.trace_font_h = int(round(self.font_height * g.line_h)) 1064 try: 1065 di.trace_labels = [self.trace_label_format % row.__dict__ for row in dataview.file.list] 1066 except: 1067 di.trace_labels = [row.Label for row in dataview.file.list] 1068 di.warnings.append('Error in Trace label format') 1069 while ( (g.trace_font_h > 8) and (max(measure_string(cr, g.trace_font_h, lbl).width for lbl in di.trace_labels) > (self.font_max_width * g.cw)) ): 1070 g.trace_font_h -= max(2, int(round(0.1*g.trace_font_h))) 1071 try: 1072 di.stimulus_labels = [self.stimulus_label_format % rec.__dict__ for rec in di.stimulus_signals] 1073 except: 1074 di.stimulus_labels = [rec.Name for rec in di.stimulus_signals] 1075 di.warnings.append('Error in Stimulus label format') 1076 if di.stimulus_labels: 1077 while ( (g.trace_font_h > 8) and (max(measure_string(cr, g.trace_font_h, lbl).width for lbl in di.stimulus_labels) > (self.font_max_width * g.cw)) ): 1078 g.trace_font_h -= max(2, int(round(0.1*g.trace_font_h))) 1079 g.stim_font_h = min(g.trace_font_h, int(round(self.font_height * (g.trace_h+2*g.tracepad_v)))) 1080 g.label_w = max(measure_string(cr, g.trace_font_h, lbl).width for lbl in di.trace_labels) 1081 if di.stimulus_labels: 1082 g.label_w = max(g.label_w, max(measure_string(cr, g.stim_font_h, lbl).width for lbl in di.stimulus_labels)) 1083 g.trace_w = g.cw - g.label_w - 3*g.tracepad_h 1084 g.dot_size = self.dot_size * g.trace_h 1085 g.line_width = self.line_width * g.trace_h 1086 g.zero_line_width = self.zero_line_width * g.trace_h 1087 1088 g.color_label = self.plot.appearance.color(COLOR_LF_LABEL) 1089 g.color_trace = self.plot.appearance.color(COLOR_LF_TRACE) 1090 g.color_idl = self.plot.appearance.color(COLOR_LF_IDL) 1091 g.color_fit = self.plot.appearance.color(COLOR_LF_FIT) 1092 g.color_stim = self.plot.appearance.color(COLOR_LF_STIMULUS) 1093 g.color_stim_idl = self.plot.appearance.color(COLOR_LF_STIMULUS_IDL) 1094 g.color_zero_line = self.plot.appearance.color(COLOR_LF_ZERO) 1095 1096 # scale bars 1097 g.scalebar_position = self.scalebar_position 1098 g.scalebar_vertical_labels = self.scalebar_vertical_labels 1099 g.scalebar_timebar = self.scalebar_timebar 1100 g.scalebar_timebar_x = self.scalebar_timebar_x 1101 g.scalebar_timebar_y = self.scalebar_timebar_y 1102 g.scalebar_yoff = 0.1 * g.trace_h 1103 g.sec_per_pix = di.maxNsample * dataview.file.sampling / g.trace_w 1104 g.font_size_num = max(2, int(round(self.scalebar_font_height * g.trace_h))) 1105 if self.scalebar_timebar_autodim: 1106 dx_sec = scalebar_length(g.trace_h * g.sec_per_pix) 1107 else: 1108 dx_sec = self.scalebar_timebar_dim 1109 g.scalebar_dx_pix = dx_pix = round(dx_sec / g.sec_per_pix) 1110 dx_scaled, dx_unit_prefix = scaled_units(dx_sec) 1111 xlabel = '%.0f' % dx_scaled 1112 di.xlabel = "%s %ss" % (xlabel, dx_unit_prefix) 1113 di.xlabel_m = measure_string(cr, g.font_size_num, di.xlabel) 1114 for rec in di.scalebar_signals: 1115 rec.sb_timebar = getattr(self, "%s_sb_timebar"%rec.Name) 1116 rec.sb_invert_x = getattr(self, "%s_sb_invert_x"%rec.Name) 1117 rec.sb_invert_y = getattr(self, "%s_sb_invert_y"%rec.Name) 1118 rec.sb_every_trace = getattr(self, "%s_sb_every_trace"%rec.Name) 1119 rec.sb_float_x = getattr(self, "%s_sb_float_x"%rec.Name) 1120 rec.sb_float_y = getattr(self, "%s_sb_float_y"%rec.Name) 1121 rec.sb_drawn = False 1122 lo, hi = rec.lo, rec.hi 1123 if getattr(self, "%s_sb_autodim"%rec.Name): 1124 dy_u = scalebar_length(0.9*abs(hi - lo)) 1125 else: 1126 dy_u = getattr(self, "%s_sb_dim"%rec.Name) 1127 rec.dy_pix = round(dy_u * g.trace_h / abs(hi - lo)) 1128 ylabel = '%.2g' % dy_u 1129 if not di.scalebar_format_fail: 1130 try: 1131 ylabel = self.scalebar_number_format % dy_u 1132 except: 1133 ylabel = '%.2g' % dy_u 1134 warnings.append('Error in Scale bar number format') 1135 di.scalebar_format_fail = True 1136 y_units = (" %s" % rec.Units) if rec.Units else "" 1137 rec.ylabel = ylabel + y_units 1138 rec.ylabel_m = measure_string(cr, g.font_size_num, rec.ylabel) 1139 1140 cr.set_line_width(g.line_width) 1141 return g, di
1142
1143 - def __onDraw(self, context, w, h, g_in=None, di_in=None):
1144 context.set_source_rgba(* self.plot.appearance.color(COLOR_LF_BG)) 1145 context.rectangle(0, 0, w, h) 1146 context.fill() 1147 if not self.__dataview: 1148 return 1149 1150 context.save() 1151 if g_in is None: 1152 g, di = self.prepare_draw(w, h, context) 1153 if g is None: 1154 context.restore() 1155 return 1156 if g.multi_file: 1157 n = len(g.sub) 1158 if n: 1159 r, c = qubx.GTK.aspect_dimensions(w or 1, h or 1, n) 1160 w1, h1 = w/c, h/r 1161 for g1, di1 in izip(g.sub, di.sub): 1162 context.save() 1163 context.translate(g1.xoff, g1.yoff) 1164 self.__onDraw(context, w1, h1, g1, di1) 1165 context.restore() 1166 context.restore() 1167 return 1168 else: 1169 g, di = g_in, di_in 1170 1171 if (di.maxNsample < (4*g.trace_w)) or self.draw_exact: 1172 xx = arange(di.maxNsample, dtype='float32') 1173 xx *= g.trace_w / di.maxNsample 1174 draw_sampled = lambda c, g, di, elx, ely, elw, elh, lo, hi, seg, color: draw_sampled_exact(c, g, di, elx, ely, elw, elh, lo, hi, seg, color, xx) 1175 1176 else: 1177 draw_sampled = draw_sampled_compressed 1178 1179 line_x = g.margin_h 1180 line_y = g.margin_v 1181 if self.scalebar_position == LISTFIG_POSITION_ABOVE: 1182 line_x, line_y = draw_scalebars(context, g, di, line_x, line_y, g.cw - 3*g.tracepad_h) 1183 if self.stimulus_position == LISTFIG_POSITION_ABOVE: 1184 line_x, line_y = draw_stimulus(context, g, di, draw_sampled, line_x, line_y) 1185 line_x, line_y = draw_traces(context, g, di, draw_sampled, line_x, line_y) 1186 if self.stimulus_position == LISTFIG_POSITION_BELOW: 1187 line_x, line_y = draw_stimulus(context, g, di, draw_sampled, line_x, line_y) 1188 if self.scalebar_position == LISTFIG_POSITION_BELOW: 1189 line_x, line_y = draw_scalebars(context, g, di, line_x, line_y, g.cw - 3*g.tracepad_h) 1190 if self.scalebar_timebar: 1191 draw_timebar(context, g, di, None, w*self.scalebar_timebar_x, h*self.scalebar_timebar_y) 1192 if self.scalebar_position == LISTFIG_POSITION_FLOAT: 1193 draw_scalebars_float(context, g, di, w, h) 1194 1195 context.restore() 1196 self.subWarnings.label = '; '.join(di.warnings) 1197 self.subWarnings.cBG = COLOR_LF_BG_TROUBLE if di.warnings else None
1198
1199 1200 -def scalebar_length(raw):
1201 if raw < 0: 1202 raw *= -1 1203 if raw == 0: 1204 raw = 1 1205 base = floor(log10(raw)) 1206 rmn = log10(raw) - base 1207 if rmn < log10(2): 1208 return 10**base 1209 if rmn < log10(5): 1210 return 2*10**base 1211 return 5*10**base
1212
1213 -def scaled_units(x):
1214 if x < 0: 1215 x *= -1 1216 for (scale, prefix) in [(1e9, 'G'), (1e6, 'M'), (1e3, 'K'), (1.0, ''), (1e-3, 'm'), (1e-6, 'u'), (1e-9, 'n'), (1e-12, 'p')]: 1217 if x >= scale: 1218 return x/scale, prefix
1219
1220 1221 -def transform_y(h, lo, hi, samples):
1222 samples *= -1.0 1223 samples += hi 1224 samples *= h / (hi - lo) 1225 return samples
1226
1227 1228 -def draw_string(context, elx, ely, elw, elh, points, s, baseline=None):
1229 m = measure_string(context, points, s) # also sets font_size 1230 if baseline is None: 1231 b = ely + (elh - m.height)/2 - m.ybearing 1232 else: 1233 b = ely + (1.0 - baseline) * elh 1234 context.move_to(elx + (elw - m.width)/2 - m.xbearing, b) 1235 context.show_text(s)
1236
1237 1238 -def draw_zero_line(context, g, di, elx, ely, elw, elh, lo, hi):
1239 if g.zero_line_width: 1240 if ((lo < hi) and (lo <= 0 <= hi)) or ((hi < lo) and (hi <= 0 <= lo)): 1241 yzero = ely + hi * (elh * 1.0 / (hi - lo)) 1242 context.set_line_width(g.zero_line_width) 1243 context.set_source_rgba(* g.color_zero_line) 1244 context.move_to(elx, yzero) 1245 context.line_to(elx+elw, yzero) 1246 context.stroke()
1247
1248 1249 -def draw_sampled_exact(context, g, di, elx, ely, elw, elh, lo, hi, seg, color, xx):
1250 context.save() 1251 context.translate(elx, ely) 1252 context.rectangle(0, 0, elw, elh) 1253 context.clip() 1254 r_in, g_in, b_in, a_in = color 1255 1256 for chunk in seg.chunks: 1257 samples = transform_y(elh, lo, hi, chunk.get_samples().samples) 1258 xx_chunk = xx[chunk.f - seg.f:chunk.l+1-seg.f] 1259 if g.dot_size: 1260 if chunk.included: 1261 context.set_source_rgba(r_in, g_in, b_in, a_in) 1262 else: 1263 context.set_source_rgba(0.5, 0.5, 0.5, 0.5) 1264 for x, y in izip(xx_chunk, samples): 1265 context.move_to(x+g.dot_size, y) 1266 context.arc(x, y, g.dot_size, 0, 2*pi) 1267 context.fill() 1268 if g.line_width: 1269 if chunk.included: 1270 context.set_source_rgba(r_in, g_in, b_in, g.line_opacity) 1271 else: 1272 context.set_source_rgba(0.5, 0.5, 0.5, 0.5*g.line_opacity) 1273 at = 0 1274 while at < (chunk.n-1): 1275 batch_n = min(chunk.n - at, 1024) 1276 context.move_to(xx_chunk[at], samples[at]) 1277 for i in xrange(at, at+batch_n): 1278 context.line_to(xx_chunk[i], samples[i]) 1279 context.stroke() 1280 at += batch_n - 1 1281 context.restore()
1282
1283 -def draw_sampled_compressed(context, g, di, elx, ely, elw, elh, lo, hi, seg, color):
1284 context.save() 1285 context.translate(elx, ely) 1286 context.rectangle(0, 0, elw, elh) 1287 context.clip() 1288 cFull = color 1289 cPartial = qubx.toolspace.SETALPHA(cFull, .5*cFull[3]) 1290 context.set_line_width(1) 1291 context.translate(0.5, 0) 1292 1293 seg_w = int(round(elw * seg.n / di.maxNsample)) 1294 samp_per_pix = di.maxNsample / elw 1295 for chunk in seg.chunks: 1296 i0 = max(0, min(seg_w-1, int(round(seg_w*float(chunk.f -seg.f)/seg.n)))) 1297 i1 = max(0, min(seg_w, int(round(seg_w*float(chunk.l+1-seg.f)/seg.n)))) 1298 samples = chunk.get_samples().samples 1299 resolution = g.line_width_input * abs(hi - lo) 1300 lows, highs, gauss_los, gauss_his = qubx.fast.data.raster_samples(i1-i0, samples, samp_per_pix, resolution) 1301 transform_y(elh, lo, hi, lows) 1302 transform_y(elh, lo, hi, highs) 1303 transform_y(elh, lo, hi, gauss_los) 1304 transform_y(elh, lo, hi, gauss_his) 1305 if chunk.included: 1306 context.set_source_rgba(*cFull) 1307 else: 1308 context.set_source_rgba(0.5, 0.5, 0.5, 0.5) 1309 for i, lo, hi in izip(count(i0), lows, highs): 1310 context.move_to(i, lo) 1311 context.line_to(i, hi) 1312 context.stroke() 1313 if chunk.included: 1314 context.set_source_rgba(*cFull) 1315 for i, lo, hi in izip(count(i0), gauss_los, gauss_his): 1316 context.move_to(i, lo) 1317 context.line_to(i, hi) 1318 context.stroke() 1319 context.restore()
1320
1321 -def draw_idealized(context, g, di, elx, ely, elw, elh, lo, hi, seg, color):
1322 context.save() 1323 context.translate(elx, ely) 1324 context.rectangle(0, 0, elw, elh) 1325 context.clip() 1326 line_width = 0.5 * g.line_width 1327 context.set_line_width(1) 1328 context.translate(0.5, 0) 1329 1330 seg_w = int(round(elw * seg.n / di.maxNsample)) 1331 lows = zeros(shape=(seg_w,), dtype='float32') 1332 highs_scaled = zeros(shape=(seg_w,), dtype='float32') 1333 lows += UNSET_IDL 1334 highs_scaled += UNSET_IDL 1335 amps = array(seg.file.ideal[seg.signal].seg[seg.index].amp, dtype='float64') 1336 seg.file.ideal[seg.signal].idl.sample_dwells(seg.f, seg.l, amps, seg_w, lows, highs_scaled) 1337 lows_scaled = array(lows, copy=True) 1338 transform_y(elh, lo, hi, lows_scaled) 1339 transform_y(elh, lo, hi, highs_scaled) 1340 for chunk in seg.chunks: 1341 if chunk.included: 1342 context.set_source_rgba(*color) 1343 else: 1344 context.set_source_rgba(0.5, 0.5, 0.5, 0.5) 1345 i0 = max(0, min(seg_w-1, int(round(seg_w*float(chunk.f -seg.f)/seg.n)))) 1346 i1 = max(0, min(seg_w, int(round(seg_w*float(chunk.l+1-seg.f)/seg.n)))) 1347 for i, lo_raw, lo, hi in izip(count(i0), lows[i0:i1], lows_scaled[i0:i1], highs_scaled[i0:i1]): 1348 if lo_raw != UNSET_IDL: 1349 context.move_to(i, lo - line_width) 1350 context.line_to(i, hi + line_width) 1351 context.stroke() 1352 context.restore()
1353
1354 -def draw_fit(context, g, di, elx, ely, elw, elh, lo, hi, seg, color):
1355 context.save() 1356 context.translate(elx, ely) 1357 context.rectangle(0, 0, elw, elh) 1358 context.clip() 1359 line_width = 0.5 * g.line_width 1360 context.set_line_width(1) 1361 context.translate(0.5, 0) 1362 1363 seg_w = int(round(elw * seg.n / di.maxNsample)) 1364 lows = zeros(shape=(seg_w,), dtype='float32') 1365 highs_scaled = zeros(shape=(seg_w,), dtype='float32') 1366 lows += UNSET_IDL 1367 highs_scaled += UNSET_IDL 1368 amps = seg.file.fits[seg.signal].idl.segmeans[seg.index] 1369 stds = seg.file.fits[seg.signal].idl.segstds[seg.index] 1370 seg.file.fits[seg.signal].idl.sample_dwells_std(seg.f, seg.l, amps, stds, seg_w, lows, highs_scaled) 1371 lows_scaled = array(lows, copy=True) 1372 transform_y(elh, lo, hi, lows_scaled) 1373 transform_y(elh, lo, hi, highs_scaled) 1374 for chunk in seg.chunks: 1375 if chunk.included: 1376 context.set_source_rgba(*color) 1377 else: 1378 context.set_source_rgba(0.5, 0.5, 0.5, 0.5) 1379 i0 = max(0, min(seg_w-1, int(round(seg_w*float(chunk.f -seg.f)/seg.n)))) 1380 i1 = max(0, min(seg_w, int(round(seg_w*float(chunk.l+1-seg.f)/seg.n)))) 1381 for i, lo_raw, lo, hi in izip(count(i0), lows[i0:i1], lows_scaled[i0:i1], highs_scaled[i0:i1]): 1382 if lo_raw != UNSET_IDL: 1383 context.move_to(i, lo - line_width) 1384 context.line_to(i, hi + line_width) 1385 context.stroke() 1386 context.restore()
1387
1388 1389 -def draw_traces(context, g, di, draw_sampled, lx, ly):
1390 for itrace in di.seg_order: 1391 context.set_source_rgba(* g.color_label) 1392 draw_string(context, lx+g.tracepad_h, ly+g.tracepad_v, g.label_w, g.line_h-2*g.tracepad_v, g.trace_font_h, di.trace_labels[itrace], baseline=g.trace_label_baseline) 1393 trace_x = lx + g.tracepad_h + g.label_w + g.tracepad_h 1394 trace_y = ly + g.tracepad_v 1395 for rec in di.trace_signals: 1396 lo, hi = rec.lo, rec.hi 1397 if g.trace_idealized_above: 1398 draw_idealized(context, g, di, trace_x, trace_y, g.trace_w, g.trace_h, lo, hi, di.signal_segs[rec.Name][itrace], g.color_idl) 1399 trace_y += g.trace_h + 2*g.tracepad_v 1400 if g.trace_zero_line: 1401 draw_zero_line(context, g, di, trace_x, trace_y, g.trace_w, g.trace_h, lo, hi) 1402 if g.trace_sampled: 1403 draw_sampled(context, g, di, trace_x, trace_y, g.trace_w, g.trace_h, lo, hi, di.signal_segs[rec.Name][itrace], g.color_trace) 1404 if g.trace_idealized and not g.trace_idealized_above: 1405 draw_idealized(context, g, di, trace_x, trace_y, g.trace_w, g.trace_h, lo, hi, di.signal_segs[rec.Name][itrace], g.color_idl) 1406 if g.trace_fit: 1407 draw_fit(context, g, di, trace_x, trace_y, g.trace_w, g.trace_h, lo, hi, di.signal_segs[rec.Name][itrace], g.color_fit) 1408 if (g.scalebar_position == LISTFIG_POSITION_RIGHT) and (rec.sb_every_trace or not rec.sb_drawn): 1409 rec.sb_drawn = True 1410 draw_scalebar_right(context, g, di, trace_x+g.tracepad_h, trace_y, g.trace_w, g.trace_h, rec) 1411 trace_y += g.trace_h + 2*g.tracepad_v 1412 ly += g.line_h 1413 return lx, ly
1414
1415 -def draw_stimulus(context, g, di, draw_sampled, lx, ly):
1416 for istim, rec in enumerate(di.stimulus_signals): 1417 lo, hi = rec.lo, rec.hi 1418 context.set_source_rgba(* g.color_label) 1419 draw_string(context, lx+g.tracepad_h, ly+g.tracepad_v, g.label_w, g.trace_h, g.stim_font_h, di.stimulus_labels[istim], baseline=g.stimulus_label_baseline) 1420 if g.stimulus_zero_line: 1421 draw_zero_line(context, g, di, lx+2*g.tracepad_h+g.label_w, ly+g.tracepad_v, g.trace_w, g.trace_h, lo, hi) 1422 if g.stimulus_sampled: 1423 draw_sampled(context, g, di, lx+2*g.tracepad_h+g.label_w, ly+g.tracepad_v, g.trace_w, g.trace_h, lo, hi, di.signal_segs[rec.Name][g.stimulus_trace], g.color_stim) 1424 if g.stimulus_idealized: 1425 draw_idealized(context, g, di, lx+2*g.tracepad_h+g.label_w, ly+g.tracepad_v, g.trace_w, g.trace_h, lo, hi, di.signal_segs[rec.Name][g.stimulus_trace], g.color_stim_idl) 1426 if (g.scalebar_position == LISTFIG_POSITION_RIGHT) and (rec.sb_every_trace or not rec.sb_drawn): 1427 rec.sb_drawn = True 1428 draw_scalebar_right(context, g, di, lx+2*g.tracepad_h+g.label_w, ly+g.tracepad_v, g.trace_w+g.tracepad_h, g.trace_h, rec) 1429 ly += g.trace_h + 2*g.tracepad_v 1430 return lx, ly
1431
1432 -def draw_timebar(context, g, di, rec, x, y):
1433 yoff = g.scalebar_yoff 1434 context.set_source_rgba(* g.color_label) 1435 context.set_line_width(g.line_width) 1436 context.move_to(x, y) 1437 xmove = (- g.scalebar_dx_pix) if (rec and rec.sb_invert_x) else g.scalebar_dx_pix 1438 context.line_to(x+xmove, y) 1439 context.stroke() 1440 lbl_x = x + 0.5*(g.scalebar_dx_pix - di.xlabel_m.width) 1441 if xmove < 0: 1442 lbl_x += xmove 1443 if rec and rec.sb_invert_y: 1444 lbl_y = y - 2*g.line_width 1445 else: 1446 lbl_y = y + di.xlabel_m.height + 2*g.line_width 1447 context.set_font_size(g.font_size_num) 1448 context.move_to(lbl_x, lbl_y) 1449 context.show_text(di.xlabel) 1450 return x + xmove
1451
1452 -def measure_timebar(g, di, rec):
1453 return (rec.sb_timebar * g.scalebar_dx_pix, di.xlabel_m.height + 3*g.line_width)
1454
1455 -def draw_ybar_label(context, g, di, rec, x, y, yoff):
1456 context.set_font_size(g.font_size_num) 1457 context.save() 1458 w, h = measure_ybar_label(g, di, rec) 1459 context.translate(x+g.line_width, y) 1460 if not rec.sb_invert_x: 1461 context.translate(-2*g.line_width - w, 0) 1462 if g.scalebar_vertical_labels: 1463 context.translate(- rec.ylabel_m.font.descent, 0) 1464 if g.scalebar_vertical_labels: 1465 if rec.sb_invert_y: 1466 context.translate(0, 0.5*(rec.dy_pix - h)) 1467 else: 1468 context.translate(0, 0.5*(rec.dy_pix - h) - rec.dy_pix) 1469 context.rotate(-pi/2) 1470 context.translate(-h, w) 1471 else: 1472 if rec.sb_invert_y: 1473 context.translate(0, rec.dy_pix - yoff) 1474 else: 1475 context.translate(0, yoff - rec.dy_pix + h) 1476 context.move_to(0, 0) 1477 context.show_text(rec.ylabel) 1478 context.restore()
1479 -def measure_ybar_label(g, di, rec):
1480 if g.scalebar_vertical_labels: 1481 return rec.ylabel_m.height, rec.ylabel_m.width 1482 else: 1483 return rec.ylabel_m.width, rec.ylabel_m.height
1484 -def draw_ybar(context, g, di, rec, x, y):
1485 yoff = g.scalebar_yoff 1486 ymove = rec.dy_pix if rec.sb_invert_y else (- rec.dy_pix) 1487 context.set_source_rgba(* g.color_label) 1488 context.set_line_width(g.line_width) 1489 context.move_to(x, y) 1490 context.line_to(x, y + ymove) 1491 context.stroke() 1492 draw_ybar_label(context, g, di, rec, x, y, yoff)
1493 -def measure_ybar(g, di, rec):
1494 return measure_ybar_label(g, di, rec)[0] + 2*g.line_width
1495
1496 -def draw_scalebar_right(context, g, di, x, y, w, h, rec):
1497 if not rec.sb_invert_y: 1498 y += h 1499 draw_scalebar(context, g, di, x+w, y, rec)
1500
1501 -def draw_scalebar_boxed(context, g, di, x, y, w, h, rec):
1502 yoff = g.scalebar_yoff 1503 at_x = w - g.tracepad_h 1504 if rec.sb_invert_x: 1505 at_x -= measure_ybar(g, di, rec) 1506 else: 1507 at_x -= measure_timebar(g, di, rec)[0] 1508 if rec.sb_invert_y: 1509 at_y = yoff 1510 else: 1511 at_y = h - yoff 1512 return draw_scalebar(context, g, di, x+at_x, y+at_y, rec)
1513
1514 -def draw_scalebar(context, g, di, x, y, rec):
1515 if rec.sb_timebar: 1516 draw_timebar(context, g, di, rec, x, y) 1517 draw_ybar(context, g, di, rec, x, y) 1518 if rec.sb_invert_x: 1519 x -= measure_timebar(g, di, rec)[0] 1520 else: 1521 x -= measure_ybar(g, di, rec) 1522 return x - g.scalebar_yoff
1523
1524 -def measure_scalebar(g, di, rec, do_xlabel=False):
1525 time_w, time_h = measure_timebar(g, di, rec) 1526 y_h = measure_ybar(g, di, rec) 1527 w = g.scalebar_yoff + time_w + y_h + g.tracepad_h 1528 # h = y_h + time_h 1529 h = g.trace_h 1530 if do_xlabel: 1531 h += time_h 1532 return (w, h)
1533
1534 -def draw_scalebars(context, g, di, lx, ly, w):
1535 w_remain = w 1536 context.save() 1537 context.translate(lx+g.tracepad_h, ly+g.tracepad_v) 1538 for i, rec in enumerate(reversed(di.scalebar_signals)): 1539 w_remain = draw_scalebar_boxed(context, g, di, 0, 0, w_remain, g.trace_h, rec) 1540 context.restore() 1541 ly += g.trace_h + 2*g.tracepad_v 1542 return lx, ly
1543
1544 -def draw_scalebars_float(context, g, di, w, h):
1545 for rec in di.scalebar_signals: 1546 draw_scalebar(context, g, di, rec.sb_float_x*w, rec.sb_float_y*h, rec)
1547 1548 # separate stimulus line width? 1549 # separate idl line width? 1550 1551 # Nb scalebars: per existing signal plus time 1552 1553 # krishna's defaults 1554 1555 # overlap or average traces 1556 # overlap all stimulus traces? 1557