import random import numpy as np from bokeh.models import ColumnDataSource from bokeh.palettes import Dark2 from dashboard_components.globals import show_spinner, hide_spinner, current_color from utils import squeeze_list class Signal: def __init__(self, name, parent, plot): self.name = name self.full_name = "{}/{}".format(parent.filename, self.name) self.plot = plot self.selected = False self.color = random.choice(Dark2[8]) self.line = None self.bands = None self.bokeh_source = parent.bokeh_source self.min_val = 0 self.max_val = 0 self.axis = 'default' self.sub_signals = [] for name in self.bokeh_source.data.keys(): if (len(name.split('/')) == 1 and name == self.name) or '/'.join(name.split('/')[:-1]) == self.name: self.sub_signals.append(name) if len(self.sub_signals) > 1: self.mean_signal = squeeze_list([name for name in self.sub_signals if 'Mean' in name.split('/')[-1]]) self.stdev_signal = squeeze_list([name for name in self.sub_signals if 'Stdev' in name.split('/')[-1]]) self.min_signal = squeeze_list([name for name in self.sub_signals if 'Min' in name.split('/')[-1]]) self.max_signal = squeeze_list([name for name in self.sub_signals if 'Max' in name.split('/')[-1]]) else: self.mean_signal = squeeze_list(self.name) self.stdev_signal = None self.min_signal = None self.max_signal = None self.has_bollinger_bands = False if self.mean_signal and self.stdev_signal and self.min_signal and self.max_signal: self.has_bollinger_bands = True self.show_bollinger_bands = False self.bollinger_bands_source = None self.update_range() def set_color(self, color): self.color = color if self.line: self.line.glyph.line_color = color if self.bands: self.bands.glyph.fill_color = color def plot_line(self): global current_color self.set_color(Dark2[8][current_color]) current_color = (current_color + 1) % len(Dark2[8]) if self.has_bollinger_bands: self.set_bands_source() self.create_bands() self.line = self.plot.line('index', self.mean_signal, source=self.bokeh_source, line_color=self.color, line_width=2) self.line.visible = True def set_selected(self, val): if self.selected != val: self.selected = val if self.line: # self.set_color(Dark2[8][current_color]) # current_color = (current_color + 1) % len(Dark2[8]) self.line.visible = self.selected if self.bands: self.bands.visible = self.selected and self.show_bollinger_bands elif self.selected: # lazy plotting - plot only when selected for the first time self.plot_line() def set_dash(self, dash): self.line.glyph.line_dash = dash def create_bands(self): self.bands = self.plot.patch(x='band_x', y='band_y', source=self.bollinger_bands_source, color=self.color, fill_alpha=0.4, alpha=0.1, line_width=0) self.bands.visible = self.show_bollinger_bands # self.min_line = plot.line('index', self.min_signal, source=self.bokeh_source, # line_color=self.color, line_width=3, line_dash="4 4") # self.max_line = plot.line('index', self.max_signal, source=self.bokeh_source, # line_color=self.color, line_width=3, line_dash="4 4") # self.min_line.visible = self.show_bollinger_bands # self.max_line.visible = self.show_bollinger_bands def set_bands_source(self): x_ticks = self.bokeh_source.data['index'] mean_values = self.bokeh_source.data[self.mean_signal] stdev_values = self.bokeh_source.data[self.stdev_signal] band_x = np.append(x_ticks, x_ticks[::-1]) band_y = np.append(mean_values - stdev_values, mean_values[::-1] + stdev_values[::-1]) source_data = {'band_x': band_x, 'band_y': band_y} if self.bollinger_bands_source: self.bollinger_bands_source.data = source_data else: self.bollinger_bands_source = ColumnDataSource(source_data) def change_bollinger_bands_state(self, new_state): self.show_bollinger_bands = new_state if self.bands and self.selected: self.bands.visible = new_state # self.min_line.visible = new_state # self.max_line.visible = new_state def update_range(self): self.min_val = np.min(self.bokeh_source.data[self.mean_signal]) self.max_val = np.max(self.bokeh_source.data[self.mean_signal]) def set_axis(self, axis): self.axis = axis if not self.line: self.plot_line() self.line.visible = False self.line.y_range_name = axis def toggle_axis(self): if self.axis == 'default': self.set_axis('secondary') else: self.set_axis('default')