Ejemplo n.º 1
0
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')
Ejemplo n.º 2
0
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')
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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')