class TVarFigureSpec(object): def __init__(self, tvar_name, auto_color=False, show_xaxis=False, slice=False, y_axis_type='log'): self.tvar_name = tvar_name self.show_xaxis = show_xaxis if 'show_all_axes' in pytplot.tplot_opt_glob: if pytplot.tplot_opt_glob['show_all_axes']: self.show_xaxis = True self.slice = slice # Variables needed across functions self.fig = None self.colors = [] self.lineglyphs = [] self.linenum = 0 self.zscale = 'log' self.zmin = 0 self.zmax = 1 self.callback = None self.slice_plot = None self.fig = Figure(x_axis_type='datetime', tools=pytplot.tplot_opt_glob['tools'], y_axis_type=self._getyaxistype()) self.fig.add_tools(BoxZoomTool(dimensions='width')) self._format() @staticmethod def get_axis_label_color(): if pytplot.tplot_opt_glob['black_background']: text_color = '#000000' else: text_color = '#FFFFFF' return text_color @staticmethod def getaxistype(): axis_type = 'time' link_y_axis = False return axis_type, link_y_axis def getfig(self): if self.slice: return [self.fig, self.slice_plot] else: return [self.fig] def setsize(self, width, height): self.fig.plot_width = width if self.show_xaxis: self.fig.plot_height = height + 22 else: self.fig.plot_height = height def add_title(self): if 'title_text' in pytplot.tplot_opt_glob: if pytplot.tplot_opt_glob['title_text'] != '': title1 = Title( text=pytplot.tplot_opt_glob['title_text'], align=pytplot.tplot_opt_glob['title_align'], text_font_size=pytplot.tplot_opt_glob['title_size'], text_color=self.get_axis_label_color()) self.fig.title = title1 self.fig.plot_height += 22 def buildfigure(self): self._setminborder() self._setxrange() self._setxaxis() self._setyrange() self._setzaxistype() self._setzrange() self._addtimebars() self._visdata() self._setxaxislabel() self._setyaxislabel() self._addhoverlines() self._addlegend() def _format(self): # Formatting stuff self.fig.grid.grid_line_color = None self.fig.axis.major_tick_line_color = None self.fig.axis.major_label_standoff = 0 self.fig.xaxis.formatter = dttf self.fig.title = None self.fig.toolbar.active_drag = 'auto' if not self.show_xaxis: self.fig.xaxis.major_label_text_font_size = '0pt' self.fig.xaxis.visible = False self.fig.lod_factor = 100 self.fig.lod_interval = 30 self.fig.lod_threshold = 100 def _setxrange(self): # Check if x range is not set, if not, set good ones if 'x_range' not in pytplot.tplot_opt_glob: pytplot.tplot_opt_glob['x_range'] = [ np.nanmin( pytplot.data_quants[self.tvar_name].coords['time'].values), np.nanmax( pytplot.data_quants[self.tvar_name].coords['time'].values) ] # Bokeh uses milliseconds since epoch for some reason x_range = Range1d( int(pytplot.tplot_opt_glob['x_range'][0]) * 1000.0, int(pytplot.tplot_opt_glob['x_range'][1]) * 1000.0) if self.show_xaxis: pytplot.lim_info['xfull'] = x_range pytplot.lim_info['xlast'] = x_range self.fig.x_range = x_range def _setyrange(self): if self._getyaxistype() == 'log': if pytplot.data_quants[self.tvar_name].attrs['plot_options']['yaxis_opt']['y_range'][0] <= 0 \ or pytplot.data_quants[self.tvar_name].attrs['plot_options']['yaxis_opt']['y_range'][1] <= 0: return y_range = Range1d( pytplot.data_quants[self.tvar_name].attrs['plot_options'] ['yaxis_opt']['y_range'][0], pytplot.data_quants[self.tvar_name].attrs['plot_options'] ['yaxis_opt']['y_range'][1]) self.fig.y_range = y_range def _setzrange(self): # Get Z Range if 'z_range' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['zaxis_opt']: self.zmin = pytplot.data_quants[self.tvar_name].attrs[ 'plot_options']['zaxis_opt']['z_range'][0] self.zmax = pytplot.data_quants[self.tvar_name].attrs[ 'plot_options']['zaxis_opt']['z_range'][1] else: dataset_temp = pytplot.data_quants[self.tvar_name].where( pytplot.data_quants[self.tvar_name] != np.inf) dataset_temp = dataset_temp.where( pytplot.data_quants[self.tvar_name] != -np.inf) self.zmax = np.float(dataset_temp.max(skipna=True).values) self.zmin = np.float(dataset_temp.min(skipna=True).values) # Cannot have a 0 minimum in a log scale if self.zscale == 'log': df = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe( self.tvar_name, no_spec_bins=True) zmin_list = [] for column in df.columns: series = df[column] zmin_list.append( series.iloc[series.to_numpy().nonzero()[0]].min()) self.zmin = min(zmin_list) def _setminborder(self): self.fig.min_border_bottom = pytplot.tplot_opt_glob[ 'min_border_bottom'] self.fig.min_border_top = pytplot.tplot_opt_glob['min_border_top'] def _addtimebars(self): for time_bar in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['time_bar']: time_bar_line = Span(location=time_bar['location'] * 1000.0, dimension=time_bar['dimension'], line_color=time_bar['line_color'], line_width=time_bar['line_width']) self.fig.renderers.extend([time_bar_line]) def _set_roi_lines(self, time): # Locating the two times between which there's a roi roi_1 = pytplot.tplot_utilities.str_to_int( pytplot.tplot_opt_glob['roi_lines'][0]) roi_2 = pytplot.tplot_utilities.str_to_int( pytplot.tplot_opt_glob['roi_lines'][1]) # find closest time to user-requested time x = np.asarray(time) x_sub_1 = abs(x - roi_1 * np.ones(len(x))) x_sub_2 = abs(x - roi_2 * np.ones(len(x))) x_argmin_1 = np.nanargmin(x_sub_1) x_argmin_2 = np.nanargmin(x_sub_2) x_closest_1 = x[x_argmin_1] x_closest_2 = x[x_argmin_2] # Create roi box roi_box = BoxAnnotation(left=x_closest_1 * 1000.0, right=x_closest_2 * 1000.0, fill_alpha=0.6, fill_color='grey', line_color='red', line_width=2.5) self.fig.renderers.extend([roi_box]) def _setxaxis(self): xaxis1 = DatetimeAxis(major_label_text_font_size='0pt', formatter=dttf) xaxis1.visible = False self.fig.add_layout(xaxis1, 'above') def _getyaxistype(self): if 'y_axis_type' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['yaxis_opt']: return pytplot.data_quants[self.tvar_name].attrs['plot_options'][ 'yaxis_opt']['y_axis_type'] else: return 'log' def _setzaxistype(self): if 'z_axis_type' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['zaxis_opt']: self.zscale = pytplot.data_quants[self.tvar_name].attrs[ 'plot_options']['zaxis_opt']['z_axis_type'] def _setcolors(self): if 'colormap' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['extras']: for cm in pytplot.data_quants[self.tvar_name].attrs[ 'plot_options']['extras']['colormap']: self.colors.append(tplot_utilities.return_bokeh_colormap(cm)) else: self.colors.append(tplot_utilities.return_bokeh_colormap('magma')) def _setxaxislabel(self): self.fig.xaxis.axis_label = pytplot.data_quants[ self.tvar_name].attrs['plot_options']['xaxis_opt']['axis_label'] self.fig.xaxis.axis_label_text_font_size = str( pytplot.data_quants[self.tvar_name].attrs['plot_options']['extras'] ['char_size']) + 'pt' self.fig.xaxis.axis_label_text_color = self.get_axis_label_color() def _setyaxislabel(self): self.fig.yaxis.axis_label = pytplot.data_quants[ self.tvar_name].attrs['plot_options']['yaxis_opt']['axis_label'] self.fig.yaxis.axis_label_text_font_size = str( pytplot.data_quants[self.tvar_name].attrs['plot_options']['extras'] ['char_size']) + 'pt' self.fig.yaxis.axis_label_text_color = self.get_axis_label_color() def _visdata(self): self._setcolors() x = pytplot.data_quants[self.tvar_name].coords['time'].values.tolist() # Add region of interest (roi) lines if applicable if 'roi_lines' in pytplot.tplot_opt_glob.keys(): self._set_roi_lines(x) temp = [ a for a in x if (a <= (pytplot.tplot_opt_glob['x_range'][1]) and a >= (pytplot.tplot_opt_glob['x_range'][0])) ] x = temp # Sometimes X will be huge, we'll need to cut down so that each x will stay about 1 pixel in size step_size = 1 num_rect_displayed = len(x) if self.fig.plot_width < num_rect_displayed: step_size = int( math.floor(num_rect_displayed / self.fig.plot_width)) x[:] = x[0::step_size] # Determine bin sizes if 'spec_bins' in pytplot.data_quants[self.tvar_name].coords: df, bins = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe( self.tvar_name) bins_vary = len(pytplot.data_quants[ self.tvar_name].coords['spec_bins'].shape) > 1 bins_increasing = pytplot.data_quants[ self.tvar_name].attrs['plot_options']['spec_bins_ascending'] else: df = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe( self.tvar_name, no_spec_bins=True) bins = pd.DataFrame( np.arange(len( pytplot.data_quants[self.tvar_name][0]))).transpose() bins_vary = False bins_increasing = True # Get length of arrays size_x = len(x) size_y = len(bins.columns) # These arrays will be populated with data for the rectangle glyphs color = [] bottom = [] top = [] left = [] right = [] value = [] corrected_time = [] # left, right, and time do not depend on the values in spec_bins for j in range(size_x - 1): left.append(x[j] * 1000.0) right.append(x[j + 1] * 1000.0) corrected_time.append(tplot_utilities.int_to_str(x[j])) left = left * (size_y - 1) right = right * (size_y - 1) corrected_time = corrected_time * (size_y - 1) # Handle the case of time-varying bin sizes if bins_vary: temp_bins = bins.loc[x[0:size_x - 1]] else: temp_bins = bins.loc[0] if bins_increasing: bin_index_range = range(0, size_y - 1, 1) else: bin_index_range = range(size_y - 1, 0, -1) for i in bin_index_range: temp = df[i][x[0:size_x - 1]].tolist() value.extend(temp) color.extend( tplot_utilities.get_heatmap_color(color_map=self.colors[0], min_val=self.zmin, max_val=self.zmax, values=temp, zscale=self.zscale)) # Handle the case of time-varying bin sizes if bins_vary: bottom.extend(temp_bins[i].tolist()) if bins_increasing: top.extend(temp_bins[i + 1].tolist()) else: top.extend(temp_bins[i - 1].tolist()) else: bottom.extend([temp_bins[i]] * (size_x - 1)) if bins_increasing: top.extend([temp_bins[i + 1]] * (size_x - 1)) else: top.extend([temp_bins[i - 1]] * (size_x - 1)) # Here is where we add all of the rectangles to the plot cds = ColumnDataSource(data=dict(x=left, y=bottom, right=right, top=top, z=color, value=value, corrected_time=corrected_time)) self.fig.quad(bottom='y', left='x', right='right', top='top', color='z', source=cds) if self.slice: if 'y_axis_type' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['yaxis_opt']: y_slice_log = 'log' else: y_slice_log = 'linear' self.slice_plot = Figure( plot_height=self.fig.plot_height, plot_width=self.fig.plot_width, y_range=(self.zmin, self.zmax), x_range=(pytplot.data_quants[self.tvar_name]. attrs['plot_options']['yaxis_opt']['y_range'][0], pytplot.data_quants[self.tvar_name]. attrs['plot_options']['yaxis_opt']['y_range'][1]), y_axis_type=y_slice_log) self.slice_plot.min_border_left = 100 spec_bins = bins flux = [0] * len(spec_bins) slice_line_source = ColumnDataSource( data=dict(x=spec_bins, y=flux)) self.slice_plot.line('x', 'y', source=slice_line_source) self.callback = CustomJS(args=dict(cds=cds, source=slice_line_source), code=""" var geometry = cb_data['geometry']; var x_data = geometry.x; // current mouse x position in plot coordinates var y_data = geometry.y; // current mouse y position in plot coordinates var d2 = source.data; var asdf = cds.data; var j = 0; x=d2['x'] y=d2['y'] time=asdf['x'] energies=asdf['y'] flux=asdf['value'] for (i = 0; i < time.length-1; i++) { if(x_data >= time[i] && x_data <= time[i+1] ) { x[j] = energies[i] y[j] = flux[i] j=j+1 } } j=0 source.change.emit(); """) def _addhoverlines(self): # Add tools hover = HoverTool(callback=self.callback) hover.tooltips = [("Time", "@corrected_time"), ("Energy", "@y"), ("Value", "@value")] self.fig.add_tools(hover) def _addlegend(self): # Add the color bar if 'z_axis_type' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['zaxis_opt']: if pytplot.data_quants[self.tvar_name].attrs['plot_options'][ 'zaxis_opt']['z_axis_type'] == 'log': color_mapper = LogColorMapper(palette=self.colors[0], low=self.zmin, high=self.zmax) color_bar = ColorBarSideTitle(color_mapper=color_mapper, ticker=LogTicker(), border_line_color=None, location=(0, 0)) color_bar.formatter = BasicTickFormatter(precision=2) else: color_mapper = LinearColorMapper(palette=self.colors[0], low=self.zmin, high=self.zmax) color_bar = ColorBarSideTitle(color_mapper=color_mapper, ticker=BasicTicker(), border_line_color=None, location=(0, 0)) color_bar.formatter = BasicTickFormatter(precision=4) else: color_mapper = LogColorMapper(palette=self.colors[0], low=self.zmin, high=self.zmax) color_bar = ColorBarSideTitle(color_mapper=color_mapper, ticker=LogTicker(), border_line_color=None, location=(0, 0)) color_bar.formatter = BasicTickFormatter(precision=2) color_bar.width = 10 color_bar.major_label_text_align = 'left' color_bar.label_standoff = 5 color_bar.major_label_text_baseline = 'middle' color_bar.title = pytplot.data_quants[ self.tvar_name].attrs['plot_options']['zaxis_opt']['axis_label'] color_bar.title_text_font_size = str( pytplot.data_quants[self.tvar_name].attrs['plot_options']['extras'] ['char_size']) + 'pt' color_bar.title_text_font_style = 'bold' color_bar.title_standoff = 20 self.fig.add_layout(color_bar, 'right')
class TVarFigureSpec(object): def __init__(self, tvar_name, auto_color=False, show_xaxis=False, interactive=False, y_axis_type='log'): self.tvar_name = tvar_name self.show_xaxis = show_xaxis self.interactive = interactive #Variables needed across functions self.fig = None self.colors = [] self.lineglyphs = [] self.linenum = 0 self.zscale = 'log' self.zmin = 0 self.zmax = 1 self.callback = None self.interactive_plot = None self.fig = Figure(x_axis_type='datetime', tools=pytplot.tplot_opt_glob['tools'], y_axis_type=self._getyaxistype()) self.fig.add_tools(BoxZoomTool(dimensions='width')) self._format() def getaxistype(self): axis_type = 'time' link_y_axis = False return axis_type, link_y_axis def getfig(self): if self.interactive: return [self.fig, self.interactive_plot] else: return [self.fig] def setsize(self, width, height): self.fig.plot_width = width if self.show_xaxis: self.fig.plot_height = height + 22 else: self.fig.plot_height = height def add_title(self): if 'title_text' in pytplot.tplot_opt_glob: if pytplot.tplot_opt_glob['title_text'] != '': title1 = Title( text=pytplot.tplot_opt_glob['title_text'], align=pytplot.tplot_opt_glob['title_align'], text_font_size=pytplot.tplot_opt_glob['title_size']) self.fig.title = title1 self.fig.plot_height += 22 def buildfigure(self): self._setminborder() self._setxrange() self._setxaxis() self._setyrange() self._setzaxistype() self._setzrange() self._addtimebars() self._visdata() self._setyaxislabel() self._setzaxislabel() self._addhoverlines() self._addlegend() def _format(self): #Formatting stuff self.fig.grid.grid_line_color = None self.fig.axis.major_tick_line_color = None self.fig.axis.major_label_standoff = 0 self.fig.xaxis.formatter = dttf self.fig.title = None self.fig.toolbar.active_drag = 'auto' if not self.show_xaxis: self.fig.xaxis.major_label_text_font_size = '0pt' self.fig.xaxis.visible = False self.fig.lod_factor = 100 self.fig.lod_interval = 30 self.fig.lod_threshold = 100 self.fig.yaxis.axis_label_text_font_size = "10pt" def _setxrange(self): #Check if x range is not set, if not, set good ones if 'x_range' not in pytplot.tplot_opt_glob: pytplot.tplot_opt_glob['x_range'] = [ np.nanmin( pytplot.data_quants[self.tvar_name].data.index.tolist()), np.nanmax( pytplot.data_quants[self.tvar_name].data.index.tolist()) ] tplot_x_range = Range1d( np.nanmin( pytplot.data_quants[self.tvar_name].data.index.tolist()), np.nanmax( pytplot.data_quants[self.tvar_name].data.index.tolist())) if self.show_xaxis: pytplot.lim_info['xfull'] = tplot_x_range pytplot.lim_info['xlast'] = tplot_x_range #Bokeh uses milliseconds since epoch for some reason x_range = Range1d(pytplot.tplot_opt_glob['x_range'][0] * 1000, pytplot.tplot_opt_glob['x_range'][1] * 1000) self.fig.x_range = x_range def _setyrange(self): if self._getyaxistype() == 'log': if pytplot.data_quants[self.tvar_name].yaxis_opt['y_range'][ 0] < 0 or pytplot.data_quants[ self.tvar_name].yaxis_opt['y_range'][1] < 0: return y_range = Range1d( pytplot.data_quants[self.tvar_name].yaxis_opt['y_range'][0], pytplot.data_quants[self.tvar_name].yaxis_opt['y_range'][1]) self.fig.y_range = y_range def _setzrange(self): #Get Z Range if 'z_range' in pytplot.data_quants[self.tvar_name].zaxis_opt: self.zmin = pytplot.data_quants[ self.tvar_name].zaxis_opt['z_range'][0] self.zmax = pytplot.data_quants[ self.tvar_name].zaxis_opt['z_range'][1] else: dataset_temp = pytplot.data_quants[self.tvar_name].data.replace( [np.inf, -np.inf], np.nan) self.zmax = dataset_temp.max().max() self.zmin = dataset_temp.min().min() #Cannot have a 0 minimum in a log scale if self.zscale == 'log': zmin_list = [] for column in pytplot.data_quants[self.tvar_name].data.columns: series = pytplot.data_quants[self.tvar_name].data[column] zmin_list.append(series.iloc[series.nonzero()[0]].min()) self.zmin = min(zmin_list) def _setminborder(self): self.fig.min_border_bottom = pytplot.tplot_opt_glob[ 'min_border_bottom'] self.fig.min_border_top = pytplot.tplot_opt_glob['min_border_top'] def _addtimebars(self): for time_bar in pytplot.data_quants[self.tvar_name].time_bar: time_bar_line = Span(location=time_bar['location'], dimension=time_bar['dimension'], line_color=time_bar['line_color'], line_width=time_bar['line_width']) self.fig.renderers.extend([time_bar_line]) def _setxaxis(self): xaxis1 = DatetimeAxis(major_label_text_font_size='0pt', formatter=dttf) xaxis1.visible = False self.fig.add_layout(xaxis1, 'above') def _getyaxistype(self): if 'y_axis_type' in pytplot.data_quants[self.tvar_name].yaxis_opt: return pytplot.data_quants[self.tvar_name].yaxis_opt['y_axis_type'] else: return 'log' def _setzaxistype(self): if 'z_axis_type' in pytplot.data_quants[self.tvar_name].zaxis_opt: self.zscale = pytplot.data_quants[ self.tvar_name].zaxis_opt['z_axis_type'] def _setcolors(self): if 'colormap' in pytplot.data_quants[self.tvar_name].extras: for cm in pytplot.data_quants[self.tvar_name].extras['colormap']: self.colors.append(tplot_utilities.return_bokeh_colormap(cm)) else: self.colors.append(tplot_utilities.return_bokeh_colormap('magma')) def _setyaxislabel(self): self.fig.yaxis.axis_label = pytplot.data_quants[ self.tvar_name].yaxis_opt['axis_label'] def _setzaxislabel(self): self.fig.yaxis.axis_label = pytplot.data_quants[ self.tvar_name].yaxis_opt['axis_label'] def _visdata(self): self._setcolors() x = pytplot.data_quants[self.tvar_name].data.index.tolist() temp = [ a for a in x if (a <= (pytplot.tplot_opt_glob['x_range'][1]) and a >= (pytplot.tplot_opt_glob['x_range'][0])) ] x = temp #Sometimes X will be huge, we'll need to cut down so that each x will stay about 1 pixel in size step_size = 1 num_rect_displayed = len(x) if (self.fig.plot_width) < num_rect_displayed: step_size = int( math.floor(num_rect_displayed / (self.fig.plot_width))) x[:] = x[0::step_size] #Determine bin sizes if pytplot.data_quants[self.tvar_name].spec_bins is not None: bins = pytplot.data_quants[self.tvar_name].spec_bins bins_vary = pytplot.data_quants[ self.tvar_name].spec_bins_time_varying bins_increasing = pytplot.data_quants[ self.tvar_name].spec_bins_ascending else: bins = pd.DataFrame( np.arange(len(pytplot.data_quants[ self.tvar_name].data.columns))).transpose() bins_vary = False bins_increasing = True #Get length of arrays size_x = len(x) size_y = len(bins.columns) #These arrays will be populated with data for the rectangle glyphs color = [] bottom = [] top = [] left = [] right = [] value = [] corrected_time = [] #left, right, and time do not depend on the values in spec_bins for j in range(size_x - 1): left.append(x[j] * 1000) right.append(x[j + 1] * 1000) corrected_time.append(tplot_utilities.int_to_str(x[j])) left = left * (size_y - 1) right = right * (size_y - 1) corrected_time = corrected_time * (size_y - 1) #Handle the case of time-varying bin sizes if bins_vary: temp_bins = bins.loc[x[0:size_x - 1]] else: temp_bins = bins.loc[0] if bins_increasing: bin_index_range = range(0, size_y - 1, 1) else: bin_index_range = range(size_y - 1, 0, -1) for i in bin_index_range: temp = pytplot.data_quants[self.tvar_name].data[i][x[0:size_x - 1]].tolist() value.extend(temp) color.extend( tplot_utilities.get_heatmap_color(color_map=self.colors[0], min_val=self.zmin, max_val=self.zmax, values=temp, zscale=self.zscale)) #Handle the case of time-varying bin sizes if bins_vary: bottom.extend(temp_bins[i].tolist()) if bins_increasing: top.extend(temp_bins[i + 1].tolist()) else: top.extend(temp_bins[i - 1].tolist()) else: bottom.extend([temp_bins[i]] * (size_x - 1)) if bins_increasing: top.extend([temp_bins[i + 1]] * (size_x - 1)) else: top.extend([temp_bins[i - 1]] * (size_x - 1)) #Here is where we add all of the rectangles to the plot cds = ColumnDataSource(data=dict(x=left, y=bottom, right=right, top=top, z=color, value=value, corrected_time=corrected_time)) self.fig.quad(bottom='y', left='x', right='right', top='top', color='z', source=cds) if self.interactive: if 'y_axis_type' in pytplot.data_quants[self.tvar_name].yaxis_opt: y_interactive_log = 'log' else: y_interactive_log = 'linear' self.interactive_plot = Figure(plot_height=self.fig.plot_height, plot_width=self.fig.plot_width, y_range=(self.zmin, self.zmax), y_axis_type=y_interactive_log) self.interactive_plot.min_border_left = 100 spec_bins = bins flux = [0] * len(spec_bins) interactive_line_source = ColumnDataSource( data=dict(x=spec_bins, y=flux)) interactive_line = Line(x='x', y='y') self.interactive_plot.add_glyph(interactive_line_source, interactive_line) self.callback = CustomJS(args=dict( cds=cds, interactive_line_source=interactive_line_source), code=""" var geometry = cb_data['geometry']; var x_data = geometry.x; // current mouse x position in plot coordinates var y_data = geometry.y; // current mouse y position in plot coordinates var d2 = interactive_line_source.get('data'); var asdf = cds.get('data'); var j = 0; x=d2['x'] y=d2['y'] time=asdf['x'] energies=asdf['y'] flux=asdf['value'] for (i = 0; i < time.length-1; i++) { if(x_data >= time[i] && x_data <= time[i+1] ) { x[j] = energies[i] y[j] = flux[i] j=j+1 } } j=0 interactive_line_source.trigger('change'); """) def _addhoverlines(self): #Add tools hover = HoverTool(callback=self.callback) hover.tooltips = [("Time", "@corrected_time"), ("Energy", "@y"), ("Value", "@value")] self.fig.add_tools(hover) def _addlegend(self): #Add the color bar if 'z_axis_type' in pytplot.data_quants[self.tvar_name].zaxis_opt: if pytplot.data_quants[ self.tvar_name].zaxis_opt['z_axis_type'] == 'log': color_mapper = LogColorMapper(palette=self.colors[0], low=self.zmin, high=self.zmax) color_bar = ColorBarSideTitle(color_mapper=color_mapper, ticker=LogTicker(), border_line_color=None, location=(0, 0)) color_bar.formatter = BasicTickFormatter(precision=2) else: color_mapper = LinearColorMapper(palette=self.colors[0], low=self.zmin, high=self.zmax) color_bar = ColorBarSideTitle(color_mapper=color_mapper, ticker=BasicTicker(), border_line_color=None, location=(0, 0)) color_bar.formatter = BasicTickFormatter(precision=4) else: color_mapper = LogColorMapper(palette=self.colors[0], low=self.zmin, high=self.zmax) color_bar = ColorBarSideTitle(color_mapper=color_mapper, ticker=LogTicker(), border_line_color=None, location=(0, 0)) color_bar.formatter = BasicTickFormatter(precision=2) color_bar.width = 10 color_bar.major_label_text_align = 'left' color_bar.label_standoff = 5 color_bar.major_label_text_baseline = 'middle' if 'axis_label' in pytplot.data_quants[self.tvar_name].zaxis_opt: color_bar.title = pytplot.data_quants[ self.tvar_name].zaxis_opt['axis_label'] color_bar.title_text_font_size = '8pt' color_bar.title_text_font_style = 'bold' color_bar.title_standoff = 20 self.fig.add_layout(color_bar, 'right')
class TVarFigureMap(object): def __init__(self, tvar_name, auto_color=False, show_xaxis=False, interactive=False): self.tvar_name = tvar_name self.show_xaxis = show_xaxis self.interactive = interactive #Variables needed across functions self.fig = None self.colors = [] self.lineglyphs = [] self.linenum = 0 self.zscale = 'linear' self.zmin = 0 self.zmax = 1 self.callback = None self.interactive_plot = None self.fig = Figure(tools="pan,crosshair,reset,box_zoom", y_axis_type=self._getyaxistype()) self._format() def getaxistype(self): axis_type = 'map' link_y_axis = True return axis_type, link_y_axis def getfig(self): if self.interactive: return [self.fig, self.interactive_plot] else: return [self.fig] def setsize(self, width, height): self.fig.plot_width = width if self.show_xaxis: self.fig.plot_height = height + 22 else: self.fig.plot_height = height def add_title(self): if 'title_text' in pytplot.tplot_opt_glob: if pytplot.tplot_opt_glob['title_text'] != '': title1 = Title( text=pytplot.tplot_opt_glob['title_text'], align=pytplot.tplot_opt_glob['title_align'], text_font_size=pytplot.tplot_opt_glob['title_size']) self.fig.title = title1 self.fig.plot_height += 22 def buildfigure(self): self._setminborder() self._setxrange() self._setxaxis() self._setyrange() self._setzrange() self._setzaxistype() self._addtimebars() self._setbackground() self._visdata() self._setyaxislabel() self._setzaxislabel() self._addhoverlines() self._addlegend() def _format(self): #Formatting stuff self.fig.grid.grid_line_color = None self.fig.axis.major_tick_line_color = None self.fig.axis.major_label_standoff = 0 self.fig.title = None self.fig.toolbar.active_drag = 'auto' if not self.show_xaxis: self.fig.xaxis.major_label_text_font_size = '0pt' self.fig.xaxis.visible = False self.fig.lod_factor = 100 self.fig.lod_interval = 30 self.fig.lod_threshold = 100 self.fig.yaxis.axis_label_text_font_size = "10pt" def _setxrange(self): #Check if x range is not set, if not, set good ones if 'map_range' not in pytplot.tplot_opt_glob: pytplot.tplot_opt_glob['map_range'] = [0, 360] tplot_x_range = Range1d(0, 360) if self.show_xaxis: pytplot.lim_info['xfull'] = tplot_x_range pytplot.lim_info['xlast'] = tplot_x_range #Bokeh uses milliseconds since epoch for some reason x_range = Range1d(pytplot.tplot_opt_glob['map_range'][0], pytplot.tplot_opt_glob['map_range'][1], bounds=(0, 360)) self.fig.x_range = x_range def _setyrange(self): y_range = Range1d(-90, 90, bounds=(-90, 90)) self.fig.y_range = y_range def _setzrange(self): #Get Z Range if 'z_range' in pytplot.data_quants[self.tvar_name].zaxis_opt: self.zmin = pytplot.data_quants[ self.tvar_name].zaxis_opt['z_range'][0] self.zmax = pytplot.data_quants[ self.tvar_name].zaxis_opt['z_range'][1] else: if isinstance(pytplot.data_quants[self.tvar_name].data, list): dataset_temp = pytplot.data_quants[pytplot.data_quants[ self.tvar_name].data[0]].data.replace([np.inf, -np.inf], np.nan) else: dataset_temp = pytplot.data_quants[ self.tvar_name].data.replace([np.inf, -np.inf], np.nan) self.zmax = dataset_temp.max().max() self.zmin = dataset_temp.min().min() #Cannot have a 0 minimum in a log scale if self.zscale == 'log': zmin_list = [] for column in dataset_temp.columns: series = dataset_temp[column] zmin_list.append(series.iloc[series.nonzero()[0]].min()) self.zmin = min(zmin_list) def _setminborder(self): self.fig.min_border_bottom = pytplot.tplot_opt_glob[ 'min_border_bottom'] self.fig.min_border_top = pytplot.tplot_opt_glob['min_border_top'] def _addtimebars(self): for time_bar in pytplot.data_quants[self.tvar_name].time_bar: time_bar_line = Span(location=time_bar['location'], dimension=time_bar['dimension'], line_color=time_bar['line_color'], line_width=time_bar['line_width']) self.fig.renderers.extend([time_bar_line]) def _setxaxis(self): #Nothing to set for now return def _getyaxistype(self): #Not going to have a log planet map return 'linear' def _setzaxistype(self): if 'z_axis_type' in pytplot.data_quants[self.tvar_name].zaxis_opt: self.zscale = pytplot.data_quants[ self.tvar_name].zaxis_opt['z_axis_type'] def _setcolors(self): if 'colormap' in pytplot.data_quants[self.tvar_name].extras: for cm in pytplot.data_quants[self.tvar_name].extras['colormap']: self.colors.append( pytplot.tplot_utilities.return_bokeh_colormap(cm)) else: self.colors.append( pytplot.tplot_utilities.return_bokeh_colormap('magma')) def _setyaxislabel(self): self.fig.yaxis.axis_label = pytplot.data_quants[ self.tvar_name].yaxis_opt['axis_label'] def _setzaxislabel(self): self.fig.yaxis.axis_label = pytplot.data_quants[ self.tvar_name].yaxis_opt['axis_label'] def _visdata(self): self._setcolors() datasets = [] if isinstance(pytplot.data_quants[self.tvar_name].data, list): for oplot_name in pytplot.data_quants[self.tvar_name].data: datasets.append(pytplot.data_quants[oplot_name]) else: datasets.append(pytplot.data_quants[self.tvar_name]) cm_index = 0 for dataset in datasets: #TODO: Add a check that lon and lat are only 1D _, x = pytplot.get_data(dataset.links['lon']) _, y = pytplot.get_data(dataset.links['lat']) for column_name in dataset.data.columns: values = dataset.data[column_name].tolist() colors = [] colors.extend( pytplot.tplot_utilities.get_heatmap_color( color_map=self.colors[cm_index], min_val=self.zmin, max_val=self.zmax, values=values, zscale=self.zscale)) circle_source = ColumnDataSource( data=dict(x=x, y=y, value=values, colors=colors)) self.fig.scatter(x='x', y='y', radius=1.0, fill_color='colors', fill_alpha=1, line_color=None, source=circle_source) cm_index += 1 def _addhoverlines(self): #Add tools hover = HoverTool() hover.tooltips = [("Longitude", "@x"), ("Latitude", "@y"), ("Value", "@value")] self.fig.add_tools(hover) def _addlegend(self): #Add the color bar if 'z_axis_type' in pytplot.data_quants[self.tvar_name].zaxis_opt: if pytplot.data_quants[ self.tvar_name].zaxis_opt['z_axis_type'] == 'log': color_mapper = LogColorMapper(palette=self.colors[0], low=self.zmin, high=self.zmax) color_bar = ColorBarSideTitle(color_mapper=color_mapper, border_line_color=None, location=(0, 0), ticker=LogTicker()) else: color_mapper = LinearColorMapper(palette=self.colors[0], low=self.zmin, high=self.zmax) color_bar = ColorBarSideTitle(color_mapper=color_mapper, border_line_color=None, location=(0, 0), ticker=BasicTicker()) else: color_mapper = LinearColorMapper(palette=self.colors[0], low=self.zmin, high=self.zmax) color_bar = ColorBarSideTitle(color_mapper=color_mapper, border_line_color=None, location=(0, 0), ticker=BasicTicker()) color_bar.width = 10 color_bar.formatter = BasicTickFormatter(precision=1) color_bar.major_label_text_align = 'left' color_bar.label_standoff = 5 color_bar.major_label_text_baseline = 'middle' if 'axis_label' in pytplot.data_quants[self.tvar_name].zaxis_opt: color_bar.title = pytplot.data_quants[ self.tvar_name].zaxis_opt['axis_label'] color_bar.title_text_font_size = '8pt' color_bar.title_text_font_style = 'bold' color_bar.title_standoff = 20 self.fig.add_layout(color_bar, 'right') def _setbackground(self): if 'alpha' in pytplot.data_quants[self.tvar_name].extras: alpha = pytplot.data_quants[self.tvar_name].extras['alpha'] else: alpha = 1 if 'basemap' in pytplot.data_quants[self.tvar_name].extras: if os.path.isfile( pytplot.data_quants[self.tvar_name].extras['basemap']): from scipy import misc img = misc.imread( pytplot.data_quants[self.tvar_name].extras['basemap'], mode='RGBA') #Need to flip the image upside down...This will probably be fixed in #a future release, so this will need to be deleted at some point img = img[::-1] self.fig.image_rgba(image=[img], x=0, y=-90, dw=360, dh=180, alpha=alpha)
class TVarFigure1D(object): def __init__(self, tvar, auto_color, last_plot=False, interactive=False): self.tvar = tvar self.auto_color = auto_color self.last_plot = last_plot self.interactive = interactive #Variables needed across functions self.colors = [ 'black', 'red', 'green', 'navy', 'orange', 'firebrick', 'pink', 'blue', 'olive' ] self.lineglyphs = [] self.linenum = 0 self.interactive_plot = None self.fig = Figure(x_axis_type='datetime', tools=pytplot.tplot_opt_glob['tools'], y_axis_type=self._getyaxistype()) self.fig.add_tools(BoxZoomTool(dimensions='width')) self._format() def getaxistype(self): axis_type = 'time' link_y_axis = False return axis_type, link_y_axis def getfig(self): if self.interactive: return [self.fig, self.interactive_plot] else: return [self.fig] def setsize(self, width, height): self.fig.plot_width = width if self.last_plot: self.fig.plot_height = height + 22 else: self.fig.plot_height = height def add_title(self): if 'title_text' in pytplot.tplot_opt_glob: if pytplot.tplot_opt_glob['title_text'] != '': title1 = Title( text=pytplot.tplot_opt_glob['title_text'], align=pytplot.tplot_opt_glob['title_align'], text_font_size=pytplot.tplot_opt_glob['title_size']) self.fig.title = title1 self.fig.plot_height += 22 def buildfigure(self): self._setminborder() self._setxrange() self._setxaxis() self._setyrange() self._addtimebars() self._visdata() self._setyaxislabel() self._addhoverlines() self._addlegend() self._addextras() def _format(self): #Formatting stuff self.fig.grid.grid_line_color = None self.fig.axis.major_tick_line_color = None self.fig.axis.major_label_standoff = 0 self.fig.xaxis.formatter = dttf self.fig.title = None self.fig.toolbar.active_drag = 'auto' if not self.last_plot: self.fig.xaxis.major_label_text_font_size = '0pt' self.fig.xaxis.visible = False def _setxrange(self): #Check if x range is not set, if not, set good ones if 'x_range' not in pytplot.tplot_opt_glob: datasets = [] x_min_list = [] x_max_list = [] if isinstance(self.tvar.data, list): for oplot_name in self.tvar.data: datasets.append(pytplot.data_quants[oplot_name].data) else: datasets.append(self.tvar.data) for dataset in datasets: x_min_list.append(np.nanmin(dataset.index.tolist())) x_max_list.append(np.nanmax(dataset.index.tolist())) pytplot.tplot_opt_glob['x_range'] = [ np.nanmin(x_min_list), np.nanmax(x_max_list) ] tplot_x_range = [np.nanmin(x_min_list), np.nanmax(x_max_list)] if self.last_plot: pytplot.lim_info['xfull'] = tplot_x_range pytplot.lim_info['xlast'] = tplot_x_range #Bokeh uses milliseconds since epoch for some reason x_range = Range1d(pytplot.tplot_opt_glob['x_range'][0] * 1000, pytplot.tplot_opt_glob['x_range'][1] * 1000) self.fig.x_range = x_range def _setyrange(self): if self._getyaxistype() == 'log': if self.tvar.yaxis_opt['y_range'][0] < 0 or self.tvar.yaxis_opt[ 'y_range'][1] < 0: return y_range = Range1d(self.tvar.yaxis_opt['y_range'][0], self.tvar.yaxis_opt['y_range'][1]) self.fig.y_range = y_range def _setminborder(self): self.fig.min_border_bottom = pytplot.tplot_opt_glob[ 'min_border_bottom'] self.fig.min_border_top = pytplot.tplot_opt_glob['min_border_top'] def _addtimebars(self): for time_bar in self.tvar.time_bar: time_bar_line = Span(location=time_bar['location'], dimension=time_bar['dimension'], line_color=time_bar['line_color'], line_width=time_bar['line_width']) self.fig.renderers.extend([time_bar_line]) def _setxaxis(self): xaxis1 = DatetimeAxis(major_label_text_font_size='0pt', formatter=dttf) xaxis1.visible = False self.fig.add_layout(xaxis1, 'above') def _getyaxistype(self): if 'y_axis_type' in self.tvar.yaxis_opt: return self.tvar.yaxis_opt['y_axis_type'] else: return 'linear' def _setcolors(self): if 'line_color' in self.tvar.extras: self.colors = self.tvar.extras['line_color'] def _setyaxislabel(self): self.fig.yaxis.axis_label = self.tvar.yaxis_opt['axis_label'] def _visdata(self): self._setcolors() datasets = [] if isinstance(self.tvar.data, list): for oplot_name in self.tvar.data: datasets.append(pytplot.data_quants[oplot_name].data) else: datasets.append(self.tvar.data) for dataset in datasets: #Get Linestyle line_style = None if 'linestyle' in self.tvar.extras: line_style = self.tvar.extras['linestyle'] #Get a list of formatted times corrected_time = [] for x in dataset.index: corrected_time.append(tplot_utilities.int_to_str(x)) #Bokeh uses milliseconds since epoch for some reason x = dataset.index * 1000 #Create lines from each column in the dataframe for column_name in dataset.columns: y = dataset[column_name] if self._getyaxistype() == 'log': y.loc[y <= 0] = np.NaN line_source = ColumnDataSource( data=dict(x=x, y=y, corrected_time=corrected_time)) if self.auto_color: line = Line(x='x', y='y', line_color=self.colors[self.linenum % len(self.colors)], **self.tvar.line_opt) else: line = Line(x='x', y='y', **self.tvar.line_opt) if 'line_style' not in self.tvar.line_opt: if line_style is not None: line.line_dash = line_style[self.linenum % len(line_style)] else: line.line_dash = self.tvar.line_opt['line_style'] self.lineglyphs.append(self.fig.add_glyph(line_source, line)) self.linenum += 1 def _addhoverlines(self): #Add tools hover = HoverTool() hover.tooltips = [("Time", "@corrected_time"), ("Value", "@y")] self.fig.add_tools(hover) def _addlegend(self): #Add the Legend if applicable if 'legend_names' in self.tvar.yaxis_opt: legend_names = self.tvar.yaxis_opt['legend_names'] if len(legend_names) != self.linenum: print("Number of lines do not match length of legend names") legend = Legend() legend.location = (0, 0) legend_items = [] j = 0 for legend_name in legend_names: legend_items.append((legend_name, [self.lineglyphs[j]])) j = j + 1 if j >= len(self.lineglyphs): break legend.items = legend_items legend.label_text_font_size = "6pt" legend.border_line_color = None legend.glyph_height = int(self.fig.plot_height / (len(legend_items) + 1)) self.fig.add_layout(legend, 'right') def _addextras(self): self.fig.renderers.extend(pytplot.extra_renderers)
class TVarFigureAlt(object): def __init__(self, tvar_name, auto_color, show_xaxis=False, interactive=False): self.tvar_name = tvar_name self.auto_color = auto_color self.show_xaxis = show_xaxis self.interactive = interactive # Variables needed across functions self.colors = ['black', 'red', 'green', 'navy', 'orange', 'firebrick', 'pink', 'blue', 'olive'] self.lineglyphs = [] self.linenum = 0 self.interactive_plot = None self.fig = Figure(tools=pytplot.tplot_opt_glob['tools'], y_axis_type=self._getyaxistype()) self.fig.add_tools(BoxZoomTool(dimensions='width')) self._format() def getaxistype(self): axis_type = 'altitude' link_y_axis = False return axis_type, link_y_axis def getfig(self): if self.interactive: return [self.fig, self.interactive_plot] else: return [self.fig] def setsize(self, width, height): self.fig.plot_width = width if self.show_xaxis: self.fig.plot_height = height + 22 else: self.fig.plot_height = height def add_title(self): if 'title_text' in pytplot.tplot_opt_glob: if pytplot.tplot_opt_glob['title_text'] != '': title1 = Title(text=pytplot.tplot_opt_glob['title_text'], align=pytplot.tplot_opt_glob['title_align'], text_font_size=pytplot.tplot_opt_glob['title_size']) self.fig.title = title1 self.fig.plot_height += 22 def buildfigure(self): self._setminborder() self._setxrange() self._setxaxis() self._setyrange() self._visdata() self._setxaxislabel() self._setyaxislabel() self._addhoverlines() self._addlegend() self._addtimebars() def _format(self): # Formatting stuff self.fig.grid.grid_line_color = None self.fig.axis.major_tick_line_color = None self.fig.axis.major_label_standoff = 0 self.fig.title = None self.fig.toolbar.active_drag = 'auto' if not self.show_xaxis: self.fig.xaxis.major_label_text_font_size = '0pt' self.fig.xaxis.visible = False def _setxrange(self): # Check if x range is not set, if not, set good ones if 'alt_range' not in pytplot.tplot_opt_glob: datasets = [] x_min_list = [] x_max_list = [] if isinstance(pytplot.data_quants[self.tvar_name].data, list): for oplot_name in pytplot.data_quants[self.tvar_name].data: datasets.append(pytplot.data_quants[oplot_name]) else: datasets.append(pytplot.data_quants[self.tvar_name]) for dataset in datasets: _, alt = pytplot.get_data(dataset.links['alt']) x_min_list.append(np.nanmin(alt.tolist())) x_max_list.append(np.nanmax(alt.tolist())) pytplot.tplot_opt_glob['alt_range'] = [np.nanmin(x_min_list), np.nanmax(x_max_list)] tplot_x_range = [np.nanmin(x_min_list), np.nanmax(x_max_list)] if self.show_xaxis: pytplot.lim_info['xfull'] = tplot_x_range pytplot.lim_info['xlast'] = tplot_x_range x_range = Range1d(pytplot.tplot_opt_glob['alt_range'][0], pytplot.tplot_opt_glob['alt_range'][1]) self.fig.x_range = x_range def _setyrange(self): if self._getyaxistype() == 'log': if pytplot.data_quants[self.tvar_name].yaxis_opt['y_range'][0] < 0 \ or pytplot.data_quants[self.tvar_name].yaxis_opt['y_range'][1] < 0: return y_range = Range1d(pytplot.data_quants[self.tvar_name].yaxis_opt['y_range'][0], pytplot.data_quants[self.tvar_name].yaxis_opt['y_range'][1]) self.fig.y_range = y_range def _setminborder(self): self.fig.min_border_bottom = pytplot.tplot_opt_glob['min_border_bottom'] self.fig.min_border_top = pytplot.tplot_opt_glob['min_border_top'] def _addtimebars(self): for time_bar in pytplot.data_quants[self.tvar_name].time_bar: time_bar_line = Span(location=time_bar['location'], dimension=time_bar['dimension'], line_color=time_bar['line_color'], line_width=time_bar['line_width'], text_font_size=str(pytplot.data_quants[self.tvar_name].extras['char_size'])+'pt') self.fig.renderers.extend([time_bar_line]) # initialize dataset variable datasets = [] # grab tbardict tbardict = pytplot.data_quants[self.tvar_name].time_bar ltbar = len(tbardict) # make sure data is in list format if isinstance(pytplot.data_quants[self.tvar_name].data, list): for oplot_name in pytplot.data_quants[self.tvar_name].data: datasets.append(pytplot.data_quants[oplot_name]) else: datasets.append(pytplot.data_quants[self.tvar_name]) for dataset in datasets: # for location in tbar dict for i in range(ltbar): # get times, color, point size test_time = pytplot.data_quants[self.tvar_name].time_bar[i]["location"] # print(test_time) color = pytplot.data_quants[self.tvar_name].time_bar[i]["line_color"] pointsize = pytplot.data_quants[self.tvar_name].time_bar[i]["line_width"] # correlate given time with corresponding data/alt points time, altitude = pytplot.get_data(dataset.links['alt']) altitude = altitude.transpose()[0] nearest_time_index = np.abs(time - test_time).argmin() data_point = dataset.data.iloc[nearest_time_index][0] alt_point = altitude[nearest_time_index] # color = pytplot.tplot_utilities.rgb_color(color) self.fig.circle([alt_point], [data_point], size=pointsize, color=color) return def _setxaxis(self): # Nothing to set for now return def _getyaxistype(self): if 'y_axis_type' in pytplot.data_quants[self.tvar_name].yaxis_opt: return pytplot.data_quants[self.tvar_name].yaxis_opt['y_axis_type'] else: return 'linear' def _setcolors(self): if 'line_color' in pytplot.data_quants[self.tvar_name].extras: self.colors = pytplot.data_quants[self.tvar_name].extras['line_color'] def _setxaxislabel(self): self.fig.xaxis.axis_label = 'Altitude' self.fig.xaxis.axis_label_text_font_size = str(pytplot.data_quants[self.tvar_name].extras['char_size'])+'pt' def _setyaxislabel(self): self.fig.yaxis.axis_label = pytplot.data_quants[self.tvar_name].yaxis_opt['axis_label'] self.fig.yaxis.axis_label_text_font_size = str(pytplot.data_quants[self.tvar_name].extras['char_size'])+'pt' def _visdata(self): self._setcolors() datasets = [] if isinstance(pytplot.data_quants[self.tvar_name].data, list): for oplot_name in pytplot.data_quants[self.tvar_name].data: datasets.append(pytplot.data_quants[oplot_name]) else: datasets.append(pytplot.data_quants[self.tvar_name]) for dataset in datasets: # Get Linestyle line_style = None if 'linestyle' in pytplot.data_quants[self.tvar_name].extras: line_style = pytplot.data_quants[self.tvar_name].extras['linestyle'] t_link, x = pytplot.get_data(dataset.links['alt']) # Create lines from each column in the dataframe for column_name in dataset.data.columns: y = dataset.data[column_name] t_tvar = dataset.data.index.values y = dataset.data[column_name].values while t_tvar[-1] > t_link[-1]: t_tvar = np.delete(t_tvar, -1) y = np.delete(y, -1) while t_tvar[0] < t_link[0]: t_tvar = np.delete(t_tvar, 0) y = np.delete(y, 0) if self._getyaxistype() == 'log': y.loc[y <= 0] = np.NaN line_source = ColumnDataSource(data=dict(x=x, y=y)) if self.auto_color: line = Line(x='x', y='y', line_color=self.colors[self.linenum % len(self.colors)], **pytplot.data_quants[self.tvar_name].line_opt) else: line = Line(x='x', y='y', **pytplot.data_quants[self.tvar_name].line_opt) if 'line_style' not in pytplot.data_quants[self.tvar_name].line_opt: if line_style is not None: line.line_dash = line_style[self.linenum % len(line_style)] else: line.line_dash = pytplot.data_quants[self.tvar_name].line_opt['line_style'] self.lineglyphs.append(self.fig.add_glyph(line_source, line)) self.linenum += 1 def _addhoverlines(self): # Add tools hover = HoverTool() hover.tooltips = [("Value", "@y")] self.fig.add_tools(hover) def _addlegend(self): # Add the Legend if applicable if 'legend_names' in pytplot.data_quants[self.tvar_name].yaxis_opt: legend_names = pytplot.data_quants[self.tvar_name].yaxis_opt['legend_names'] if len(legend_names) != self.linenum: print("Number of lines do not match length of legend names") legend = Legend() legend.location = (0, 0) legend_items = [] j = 0 for legend_name in legend_names: legend_items.append((legend_name, [self.lineglyphs[j]])) j = j+1 if j >= len(self.lineglyphs): break legend.items = legend_items legend.label_text_font_size = str(pytplot.data_quants[self.tvar_name].extras['char_size'])+'pt' legend.border_line_color = None legend.glyph_height = int(self.fig.plot_height / (len(legend_items) + 1)) self.fig.add_layout(legend, 'right')
class TVarFigureMap(object): def __init__(self, tvar_name, auto_color=False, show_xaxis=False, slice=False): self.tvar_name = tvar_name self.show_xaxis = show_xaxis self.slice = slice # Variables needed across functions self.fig = None self.colors = [] self.lineglyphs = [] self.linenum = 0 self.zscale = 'linear' self.zmin = 0 self.zmax = 1 self.callback = None self.interactive_plot = None self.fig = Figure(tools="pan,crosshair,reset,box_zoom", y_axis_type=self._getyaxistype()) self._format() @staticmethod def get_axis_label_color(): if pytplot.tplot_opt_glob['black_background']: text_color = '#000000' else: text_color = '#FFFFFF' return text_color @staticmethod def getaxistype(): axis_type = 'map' link_y_axis = True return axis_type, link_y_axis def getfig(self): if self.slice: return [self.fig, self.interactive_plot] else: return [self.fig] def setsize(self, width, height): self.fig.plot_width = width if self.show_xaxis: self.fig.plot_height = height + 22 else: self.fig.plot_height = height def add_title(self): if 'title_text' in pytplot.tplot_opt_glob: if pytplot.tplot_opt_glob['title_text'] != '': title1 = Title( text=pytplot.tplot_opt_glob['title_text'], align=pytplot.tplot_opt_glob['title_align'], text_font_size=pytplot.tplot_opt_glob['title_size'], text_color=self.get_axis_label_color()) self.fig.title = title1 self.fig.plot_height += 22 def buildfigure(self): self._setminborder() self._setxrange() self._setxaxis() self._setyrange() self._setzrange() self._setzaxistype() self._setbackground() self._visdata() self._setxaxislabel() self._setyaxislabel() self._addhoverlines() self._addlegend() self._addtimebars() def _format(self): # Formatting stuff self.fig.grid.grid_line_color = None self.fig.axis.major_tick_line_color = None self.fig.axis.major_label_standoff = 0 self.fig.title = None self.fig.toolbar.active_drag = 'auto' if not self.show_xaxis: self.fig.xaxis.major_label_text_font_size = '0pt' self.fig.xaxis.visible = False self.fig.lod_factor = 100 self.fig.lod_interval = 30 self.fig.lod_threshold = 100 def _setxrange(self): # Check if x range is not set, if not, set good ones if 'map_range' not in pytplot.tplot_opt_glob: pytplot.tplot_opt_glob['map_range'] = [0, 360] tplot_x_range = Range1d(0, 360) if self.show_xaxis: pytplot.lim_info['xfull'] = tplot_x_range pytplot.lim_info['xlast'] = tplot_x_range # Bokeh uses milliseconds since epoch for some reason x_range = Range1d(pytplot.tplot_opt_glob['map_range'][0], pytplot.tplot_opt_glob['map_range'][1], bounds=(0, 360)) self.fig.x_range = x_range def _setyrange(self): y_range = Range1d(-90, 90, bounds=(-90, 90)) self.fig.y_range = y_range def _setzrange(self): # Get Z Range if 'z_range' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['zaxis_opt']: self.zmin = pytplot.data_quants[self.tvar_name].attrs[ 'plot_options']['zaxis_opt']['z_range'][0] self.zmax = pytplot.data_quants[self.tvar_name].attrs[ 'plot_options']['zaxis_opt']['z_range'][1] else: dataset_temp = pytplot.data_quants[self.tvar_name].where( pytplot.data_quants[self.tvar_name] != np.inf) dataset_temp = dataset_temp.where( pytplot.data_quants[self.tvar_name] != -np.inf) self.zmax = np.float(dataset_temp.max(skipna=True).values) self.zmin = np.float(dataset_temp.min(skipna=True).values) # Cannot have a 0 minimum in a log scale if self.zscale == 'log': df = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe( self.tvar_name, no_spec_bins=True) zmin_list = [] for column in df.columns: series = df[column] zmin_list.append( series.iloc[series.to_numpy().nonzero()[0]].min()) self.zmin = min(zmin_list) def _setminborder(self): self.fig.min_border_bottom = pytplot.tplot_opt_glob[ 'min_border_bottom'] self.fig.min_border_top = pytplot.tplot_opt_glob['min_border_top'] def _addtimebars(self): for time_bar in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['time_bar']: time_bar_line = Span(location=time_bar['location'], dimension=time_bar['dimension'], line_color=time_bar['line_color'], line_width=time_bar['line_width']) self.fig.renderers.extend([time_bar_line]) # grab tbardict tbardict = pytplot.data_quants[ self.tvar_name].attrs['plot_options']['time_bar'] ltbar = len(tbardict) # make sure data is in list format datasets = [pytplot.data_quants[self.tvar_name]] for oplot_name in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['overplots']: datasets.append(pytplot.data_quants[oplot_name]) for dataset in datasets: # for location in tbar dict for i in range(ltbar): # get times, color, point size test_time = pytplot.data_quants[self.tvar_name].attrs[ 'plot_options']['time_bar'][i]["location"] color = pytplot.data_quants[self.tvar_name].attrs[ 'plot_options']['time_bar'][i]["line_color"] pointsize = pytplot.data_quants[self.tvar_name].attrs[ 'plot_options']['time_bar'][i]["line_width"] # correlate given time with corresponding lat/lon points time = pytplot.data_quants[dataset.attrs['plot_options'] ['links']['lat']].coords['time'] latitude = pytplot.data_quants[dataset.attrs['plot_options'] ['links']['lat']].values longitude = pytplot.data_quants[dataset.attrs['plot_options'] ['links']['lon']].values nearest_time_index = np.abs(time - test_time).argmin() lat_point = latitude[nearest_time_index] lon_point = longitude[nearest_time_index] # color = pytplot.tplot_utilities.rgb_color(color) self.fig.circle([lon_point], [lat_point], size=pointsize, color=color) return def _setxaxis(self): # Nothing to set for now return def _getyaxistype(self): # Not going to have a log planet map return 'linear' def _setzaxistype(self): if 'z_axis_type' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['zaxis_opt']: self.zscale = pytplot.data_quants[self.tvar_name].attrs[ 'plot_options']['zaxis_opt']['z_axis_type'] def _setcolors(self): if 'colormap' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['extras']: for cm in pytplot.data_quants[self.tvar_name].attrs[ 'plot_options']['extras']['colormap']: self.colors.append( pytplot.tplot_utilities.return_bokeh_colormap(cm)) else: self.colors.append( pytplot.tplot_utilities.return_bokeh_colormap('magma')) def _setxaxislabel(self): self.fig.xaxis.axis_label = 'Longitude' self.fig.xaxis.axis_label_text_font_size = str( pytplot.data_quants[self.tvar_name].attrs['plot_options']['extras'] ['char_size']) + 'pt' self.fig.xaxis.axis_label_text_color = self.get_axis_label_color() def _setyaxislabel(self): self.fig.yaxis.axis_label = 'Latitude' self.fig.yaxis.axis_label_text_font_size = str( pytplot.data_quants[self.tvar_name].attrs['plot_options']['extras'] ['char_size']) + 'pt' self.fig.yaxis.axis_label_text_color = self.get_axis_label_color() def _visdata(self): self._setcolors() datasets = [pytplot.data_quants[self.tvar_name]] for oplot_name in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['overplots']: datasets.append(pytplot.data_quants[oplot_name]) cm_index = 0 for dataset in datasets: # TODO: Add a check that lon and lat are only 1D coords = pytplot.tplot_utilities.return_interpolated_link_dict( dataset, ['lon', 'lat']) t_link_lon = coords['lon'].coords['time'].values x = coords['lon'].values t_link_lat = coords['lat'].coords['time'].values y = coords['lon'].values df = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe( dataset.name, no_spec_bins=True) for column_name in df.columns: data = df[column_name].values # Need to trim down the data points to fit within the link t_tvar = df.index.values while t_tvar[-1] > t_link_lon[-1]: t_tvar = np.delete(t_tvar, -1) data = np.delete(data, -1) while t_tvar[0] < t_link_lon[0]: t_tvar = np.delete(t_tvar, 0) data = np.delete(data, 0) while t_tvar[-1] > t_link_lat[-1]: t_tvar = np.delete(t_tvar, -1) data = np.delete(data, -1) while t_tvar[0] < t_link_lat[0]: t_tvar = np.delete(t_tvar, 0) data = np.delete(data, 0) colors = [] colors.extend( pytplot.tplot_utilities.get_heatmap_color( color_map=self.colors[cm_index % len(self.colors)], min_val=self.zmin, max_val=self.zmax, values=data.tolist(), zscale=self.zscale)) circle_source = ColumnDataSource( data=dict(x=x, y=y, value=data.tolist(), colors=colors)) self.fig.scatter(x='x', y='y', radius=1.0, fill_color='colors', fill_alpha=1, line_color=None, source=circle_source) cm_index += 1 def _addhoverlines(self): # Add tools hover = HoverTool() hover.tooltips = [("Longitude", "@x"), ("Latitude", "@y"), ("Value", "@value")] self.fig.add_tools(hover) def _addlegend(self): # Add the color bar if 'z_axis_type' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['zaxis_opt']: if pytplot.data_quants[self.tvar_name].attrs['plot_options'][ 'zaxis_opt']['z_axis_type'] == 'log': color_mapper = LogColorMapper(palette=self.colors[0], low=self.zmin, high=self.zmax) color_bar = ColorBarSideTitle(color_mapper=color_mapper, border_line_color=None, location=(0, 0), ticker=LogTicker()) else: color_mapper = LinearColorMapper(palette=self.colors[0], low=self.zmin, high=self.zmax) color_bar = ColorBarSideTitle(color_mapper=color_mapper, border_line_color=None, location=(0, 0), ticker=BasicTicker()) else: color_mapper = LinearColorMapper(palette=self.colors[0], low=self.zmin, high=self.zmax) color_bar = ColorBarSideTitle(color_mapper=color_mapper, border_line_color=None, location=(0, 0), ticker=BasicTicker()) color_bar.width = 10 color_bar.formatter = BasicTickFormatter(precision=1) color_bar.major_label_text_align = 'left' color_bar.label_standoff = 5 color_bar.major_label_text_baseline = 'middle' color_bar.title = pytplot.data_quants[ self.tvar_name].attrs['plot_options']['zaxis_opt']['axis_label'] color_bar.title_text_font_size = str( pytplot.data_quants[self.tvar_name].attrs['plot_options']['extras'] ['char_size']) + 'pt' color_bar.title_text_font_style = 'bold' color_bar.title_standoff = 20 self.fig.add_layout(color_bar, 'right') def _setbackground(self): if 'alpha' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['extras']: alpha = pytplot.data_quants[ self.tvar_name].attrs['plot_options']['extras']['alpha'] else: alpha = 1 if 'basemap' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['extras']: if os.path.isfile( pytplot.data_quants[self.tvar_name].attrs['plot_options'] ['extras']['basemap']): from scipy import misc img = misc.imread(pytplot.data_quants[ self.tvar_name].attrs['plot_options']['extras']['basemap'], mode='RGBA') # Need to flip the image upside down...This will probably be fixed in # a future release, so this will need to be deleted at some point img = img[::-1] self.fig.image_rgba(image=[img], x=0, y=-90, dw=360, dh=180, alpha=alpha)
class TVarFigure1D(object): def __init__(self, tvar_name, auto_color, show_xaxis=False, interactive=False): self.tvar_name = tvar_name self.auto_color = auto_color self.show_xaxis = show_xaxis if 'show_all_axes' in pytplot.tplot_opt_glob: if pytplot.tplot_opt_glob['show_all_axes']: self.show_xaxis = True self.interactive = interactive # Variables needed across functions self.colors = [ 'black', 'red', 'green', 'navy', 'orange', 'firebrick', 'pink', 'blue', 'olive' ] self.lineglyphs = [] self.linenum = 0 self.interactive_plot = None self.fig = Figure(x_axis_type='datetime', tools=pytplot.tplot_opt_glob['tools'], y_axis_type=self._getyaxistype()) self.fig.add_tools(BoxZoomTool(dimensions='width')) self._format() @staticmethod def get_axis_label_color(): if pytplot.tplot_opt_glob['black_background']: text_color = '#000000' else: text_color = '#FFFFFF' return text_color @staticmethod def getaxistype(): axis_type = 'time' link_y_axis = False return axis_type, link_y_axis def getfig(self): if self.interactive: return [self.fig, self.interactive_plot] else: return [self.fig] def setsize(self, width, height): self.fig.plot_width = width if self.show_xaxis: self.fig.plot_height = height + 22 else: self.fig.plot_height = height def add_title(self): if 'title_text' in pytplot.tplot_opt_glob: if pytplot.tplot_opt_glob['title_text'] != '': title1 = Title( text=pytplot.tplot_opt_glob['title_text'], align=pytplot.tplot_opt_glob['title_align'], text_font_size=pytplot.tplot_opt_glob['title_size'], text_color=self.get_axis_label_color()) self.fig.title = title1 self.fig.plot_height += 22 def buildfigure(self): self._setminborder() self._setxrange() self._setxaxis() self._setyrange() self._addtimebars() self._visdata() self._setxaxislabel() self._setyaxislabel() self._addhoverlines() self._addlegend() def _format(self): # Formatting stuff self.fig.grid.grid_line_color = None self.fig.axis.major_tick_line_color = None self.fig.axis.major_label_standoff = 0 self.fig.xaxis.formatter = dttf self.fig.title = None self.fig.toolbar.active_drag = 'auto' if not self.show_xaxis: self.fig.xaxis.major_label_text_font_size = '0pt' self.fig.xaxis.visible = False def _setxrange(self): # Check if x range is not set, if not, set good ones if 'x_range' not in pytplot.tplot_opt_glob: datasets = [pytplot.data_quants[self.tvar_name]] x_min_list = [] x_max_list = [] for oplot_name in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['overplots']: datasets.append(pytplot.data_quants[oplot_name]) for dataset in datasets: x_min_list.append(np.nanmin(dataset.coords['time'])) x_max_list.append(np.nanmax(dataset.coords['time'])) pytplot.tplot_opt_glob['x_range'] = [ np.nanmin(x_min_list), np.nanmax(x_max_list) ] tplot_x_range = [np.nanmin(x_min_list), np.nanmax(x_max_list)] if self.show_xaxis: pytplot.lim_info['xfull'] = tplot_x_range pytplot.lim_info['xlast'] = tplot_x_range # Bokeh uses milliseconds since epoch for some reason x_range = Range1d( int(pytplot.tplot_opt_glob['x_range'][0]) * 1000.0, int(pytplot.tplot_opt_glob['x_range'][1]) * 1000.0) self.fig.x_range = x_range def _setyrange(self): if self._getyaxistype() == 'log': if pytplot.data_quants[self.tvar_name].attrs['plot_options']['yaxis_opt']['y_range'][0] < 0 or \ pytplot.data_quants[self.tvar_name].attrs['plot_options']['yaxis_opt']['y_range'][1] < 0: return y_range = Range1d( pytplot.data_quants[self.tvar_name].attrs['plot_options'] ['yaxis_opt']['y_range'][0], pytplot.data_quants[self.tvar_name].attrs['plot_options'] ['yaxis_opt']['y_range'][1]) self.fig.y_range = y_range def _setminborder(self): self.fig.min_border_bottom = pytplot.tplot_opt_glob[ 'min_border_bottom'] self.fig.min_border_top = pytplot.tplot_opt_glob['min_border_top'] if 'vertical_spacing' in pytplot.tplot_opt_glob: self.fig.min_border_bottom = int( pytplot.tplot_opt_glob['vertical_spacing'] / 2.0) self.fig.min_border_top = int( pytplot.tplot_opt_glob['vertical_spacing'] / 2.0) def _addtimebars(self): for time_bar in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['time_bar']: time_bar_line = Span(location=time_bar['location'] * 1000.0, dimension=time_bar['dimension'], line_color=time_bar['line_color'], line_width=time_bar['line_width']) self.fig.renderers.extend([time_bar_line]) def _set_roi_lines(self, dataset): # Locating the two times between which there's a roi time = dataset.coords['time'].values roi_1 = pytplot.tplot_utilities.str_to_int( pytplot.tplot_opt_glob['roi_lines'][0]) roi_2 = pytplot.tplot_utilities.str_to_int( pytplot.tplot_opt_glob['roi_lines'][1]) # find closest time to user-requested time x = np.asarray(time) x_sub_1 = abs(x - roi_1 * np.ones(len(x))) x_sub_2 = abs(x - roi_2 * np.ones(len(x))) x_argmin_1 = np.nanargmin(x_sub_1) x_argmin_2 = np.nanargmin(x_sub_2) x_closest_1 = x[x_argmin_1] x_closest_2 = x[x_argmin_2] # Create roi box roi_box = BoxAnnotation(left=x_closest_1 * 1000.0, right=x_closest_2 * 1000.0, fill_alpha=0.2, fill_color='grey', line_color='red', line_width=2.5) self.fig.renderers.extend([roi_box]) def _setxaxis(self): xaxis1 = DatetimeAxis(major_label_text_font_size='0pt', formatter=dttf) xaxis1.visible = False self.fig.add_layout(xaxis1, 'above') def _getyaxistype(self): if 'y_axis_type' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['yaxis_opt']: return pytplot.data_quants[self.tvar_name].attrs['plot_options'][ 'yaxis_opt']['y_axis_type'] else: return 'linear' def _setcolors(self): if 'line_color' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['extras']: self.colors = pytplot.data_quants[ self.tvar_name].attrs['plot_options']['extras']['line_color'] def _setxaxislabel(self): self.fig.xaxis.axis_label = pytplot.data_quants[ self.tvar_name].attrs['plot_options']['xaxis_opt']['axis_label'] self.fig.xaxis.axis_label_text_font_size = str( pytplot.data_quants[self.tvar_name].attrs['plot_options']['extras'] ['char_size']) + 'pt' self.fig.xaxis.axis_label_text_color = self.get_axis_label_color() def _setyaxislabel(self): self.fig.yaxis.axis_label = pytplot.data_quants[ self.tvar_name].attrs['plot_options']['yaxis_opt']['axis_label'] self.fig.yaxis.axis_label_text_font_size = str( pytplot.data_quants[self.tvar_name].attrs['plot_options']['extras'] ['char_size']) + 'pt' self.fig.yaxis.axis_label_text_color = self.get_axis_label_color() def _visdata(self): self._setcolors() datasets = [pytplot.data_quants[self.tvar_name]] for oplot_name in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['overplots']: datasets.append(pytplot.data_quants[oplot_name]) for dataset in datasets: # Get Linestyle line_style = None if 'linestyle' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['extras']: line_style = pytplot.data_quants[self.tvar_name].attrs[ 'plot_options']['extras']['linestyle'] # Get a list of formatted times corrected_time = [] for x in dataset.coords['time'].values: corrected_time.append(tplot_utilities.int_to_str(x)) # Bokeh uses milliseconds since epoch for some reason x = dataset.coords['time'].values * 1000.0 # Add region of interest (roi) lines if applicable if 'roi_lines' in pytplot.tplot_opt_glob.keys(): self._set_roi_lines(dataset) plot_options = dataset.attrs['plot_options'] df = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe( dataset.name) # Create lines from each column in the dataframe for column_name in df.columns: y = df[column_name] # Account for log plotting if self._getyaxistype() == 'log': y.loc[y <= 0] = np.NaN if 'line_style' in plot_options['line_opt']: if plot_options['line_opt']['line_style'] == 'scatter': Glyph = X else: Glyph = Line else: Glyph = Line # Until what size of a data gap are we removing nan values from the dataset? Set by the user # (default is to plot as bokeh would normally plot w/o worrying about data gap handling). limit = pytplot.tplot_opt_glob['data_gap'] if limit != 0: # Grabbing the times associated with nan values (nan_values), and the associated "position" of those # keys in the dataset list (nan_keys) nan_values = y[y.isnull().values].index.tolist() nan_keys = [y.index.tolist().index(j) for j in nan_values] nans = dict(zip(nan_keys, nan_values)) count = 0 # Keeping a count of how big of a time gap we have consec_list = list( ) # List of consecutive nan values (composed of indices for gaps not bigger than # the user-specified data gap) for val in range(len(nan_keys)): # Avoiding some weird issues with going to the last data point in the nan dictionary keys if val != (len(nan_keys) - 1): # Difference between one index and another - if consecutive indices, the diff will be 1 diff = abs(nan_keys[val] - nan_keys[val + 1]) # calculate time accumulated from one index to the next t_now = nan_values[val] t_next = nan_values[val + 1] time_accum = abs(t_now - t_next) # If we haven't reached the allowed data gap, just keep track of how big of a gap we're at, # and the indices in the gap if diff == 1 and count < limit: count += time_accum consec_list.append(nan_keys[val]) # This triggers when we initially exceed the allowed data gap elif diff == 1 and count >= limit: pass # When we find that the previous index and the current one are not consecutive, stop adding to # the consec_list/overall_list (if applicable), and start over the count of time accumulated # in a gap, as well as the consecutive list of time values with nans elif diff != 1: # Restart the count and add the current val to the list of nan values to remove count = 0 consec_list.append(nan_keys[val]) times = x.tolist() for elem in consec_list: # Unless the data gap was big enough, we need to remove nan values from the data, # otherwise bokeh will automatically NOT interpolate (the exact opposite of behavior in # pyqtgraph, which ALWAYS interpolates...). times.remove(nans[elem] * 1000.0) del y[nans[elem]] del corrected_time[corrected_time.index( tplot_utilities.int_to_str(nans[elem]))] # Data to be plotted line_source = ColumnDataSource( data=dict(x=times, y=y, corrected_time=corrected_time)) else: # Data to be plotted line_source = ColumnDataSource( data=dict(x=x, y=y, corrected_time=corrected_time)) if self.auto_color: line = Glyph(x='x', y='y', line_color=self.colors[self.linenum % len(self.colors)]) else: line = Glyph(x='x', y='y') if Glyph == Line: if 'line_style' not in plot_options['line_opt']: if line_style is not None: line.line_dash = line_style[self.linenum % len(line_style)] else: line.line_dash = plot_options['line_style'] self.lineglyphs.append(self.fig.add_glyph(line_source, line)) self.linenum += 1 def _addhoverlines(self): # Add tools hover = HoverTool() hover.tooltips = [("Time", "@corrected_time"), ("Value", "@y")] self.fig.add_tools(hover) def _addlegend(self): # Add the Legend if applicable if 'legend_names' in pytplot.data_quants[ self.tvar_name].attrs['plot_options']['yaxis_opt']: legend_names = pytplot.data_quants[self.tvar_name].attrs[ 'plot_options']['yaxis_opt']['legend_names'] if len(legend_names) != self.linenum: print("Number of lines do not match length of legend names") legend = Legend() legend.location = (0, 0) legend_items = [] j = 0 for legend_name in legend_names: legend_items.append((legend_name, [self.lineglyphs[j]])) j = j + 1 if j >= len(self.lineglyphs): break legend.items = legend_items legend.label_text_font_size = str( pytplot.data_quants[self.tvar_name].attrs['plot_options'] ['extras']['char_size']) + 'pt' legend.border_line_color = None legend.glyph_height = int(self.fig.plot_height / (len(legend_items) + 1)) self.fig.add_layout(legend, 'right')