only_visible=True, follow="end", follow_interval=span * 1000, max_interval=60 * 1000 * 60 * 24, min_interval=1000, range_padding_units='absolute', range_padding=1000, )) #other_property = here) p.yaxis.visible = False p.xaxis.formatter = DatetimeTickFormatter(milliseconds='%H:%M:%S.%2N', seconds="%H:%M:%S", minsec="%H:%M:%S", minutes="%H:%M:%S", hourmin="%H:%M:%S", hours="%H:%M:%S", days=['%m/%d', '%a%d'], months=['%m/%Y', '%b %Y'], years=['%Y']) r1 = p.line([], [], color="yellow", line_width=line_width, y_range_name="pressures") r2 = p.line([], [], color="skyblue", line_width=line_width, y_range_name="temps") r3 = p.line([], [], color="green", line_width=line_width, y_range_name="temps")
def line_plot(fig, x, y, source, color, label=None, x_axis_type='date', **kwargs): """ Add a single time series to a :obj:`bokeh.plotting.figure.Figure` object using data from a datetime indexed :obj:`pandas.DataFrame` with an interactive hover tool. Interactive hover shows the values of all time series data and date that is added to the figure. Arguments: fig (:obj:`bokeh.plotting.figure.Figure`): a figure instance to add the line to. x (str): name of the datetime index or column in the :obj:`pandas.DataFrame` containing data to plot. y (str): name of the column in the :obj:`pandas.DataFrame` to plot. source (:obj:`bokeh.models.sources.ColumnDataSource`): column data source created from the :obj:`pandas.DataFrame` with data to plot. color (str): color of plot line, see Bokeh for color options. label (str or :obj:`None`): default :obj:`None`. Label for plot legend (for ``y``). x_axis_type (:obj:`str` or :obj:`None`): default 'date'. If "date" then the x-axis will be formatted as month-day-year. Returns: :obj:`None` Example: To use the :meth:`Plot.line_plot` function we first need to create a :obj:`bokeh.models.sources.ColumnDataSource` from a :obj:`pandas.DataFrame`. Let's say we want to plot the monthly time series of corrected latent energy, starting from a config.ini file, >>> from fluxdataqaqc import Data, QaQc, Plot >>> d = Data('path/to/config.ini') >>> q = QaQc(d) >>> q.correct_data() Now the :obj:`.QaQc` should have the "LE_corr" (corrected latent energy) column, we can now make a :obj:`bokeh.models.sources.ColumnDataSource` from :attr:`fluxdataqaqc.QaQc.df` or :attr:`fluxdataqaqc.QaQc.monthly_df`, >>> from bokeh.plotting import ColumnDataSource, figure, show >>> source = ColumnDataSource(q.monthly_df) >>> # create the figure before using line_plot >>> fig = figure(x_axis_label='date', y_axis_label='Corrected LE') >>> Plot.line_plot( >>> fig, 'date', 'LE_corr', source, color='red', line_width=3 >>> ) >>> show(fig) Notice, ``line_width`` is not an argument to :meth:`Plot.line_plot` but it is an acceptable keyword argument to :obj:`bokeh.plotting.figure.Figure` and therefore will work as expected. Note: This method is also available from the :obj:`.Data` and :obj:`.QaQc` objects. """ hover_mode = kwargs.pop('mode','vline') if label is None: fig.line(x,y, source=source, color=color, **kwargs) else: #label=dict(value=label) # old label requirement bokeh < 2? fig.line(x,y,source=source,color=color,legend_label=label,**kwargs) fig.legend.location = "top_left" fig.legend.click_policy="hide" # add Hover tool with additional tips if more than one line if len(fig.hover) == 0: if x_axis_type == 'date': Hover = HoverTool( tooltips=[ (x,'@{}{}'.format(x,'{%F}')), (y,'@{}'.format(y)) ], formatters={'@{}'.format(x): 'datetime'}, mode = hover_mode ) fig.add_tools(Hover) else: Hover = HoverTool( tooltips=[ (x,'@{}'.format(x)), (y,'@{}'.format(y)) ], mode = hover_mode ) fig.add_tools(Hover) else: fig.hover[0].tooltips.append((y,'@{}'.format(y))) # enforce datetime x axis it date if x_axis_type == 'date': fig.xaxis.formatter = DatetimeTickFormatter(days="%d-%b-%Y")
import pandas as pd from bokeh.models import HoverTool from bokeh.models.formatters import DatetimeTickFormatter from bokeh.plotting import figure, ColumnDataSource from app import db from app.decorators import data_quality # creates your plot date_formatter = DatetimeTickFormatter(microseconds=['%f'], milliseconds=['%S.%2Ns'], seconds=[':%Ss'], minsec=[':%Mm:%Ss'], minutes=['%H:%M:%S'], hourmin=['%H:%M:'], hours=["%H:%M"], days=["%d %b"], months=["%d %b %Y"], years=["%b %Y"]) @data_quality(name='hbdet_bias', caption=' ') def hbdet_bias_plot(start_date, end_date): """Return a <div> element with a weather downtime plot. The plot shows the downtime for the period between start_date (inclusive) and end_date (exclusive). Params: ------- start_date: date
def set_graph_and_legend_properties(plot_graph: figure(), graph_title: str) -> figure(): """ Sets the Properties of the graph and the legend of the graph :param plot_graph: :param legends: :param graph_title: :return: """ # Add Tool - Hovertool # Hover Tool Properties # @Todo - Add hovertool tips # hover_tool_tips = set_hover_tool_tips() # plot_graph.add_tools(hover_tool_tips) # Remove Logo plot_graph.toolbar.logo = None # Legend related formatting # legend = Legend(items=legends, location=(0, 0)) plot_graph.legend.click_policy = "hide" plot_graph.legend.background_fill_color = "#2F2F2F" plot_graph.legend.label_text_color = "white" plot_graph.legend.border_line_color = "#2F2F2F" plot_graph.legend.inactive_fill_color = "#2F2F2F" plot_graph.legend.location = "top_left" # plot_graph.add_layout(legend, 'right') # X-Axis related formatting plot_graph.xgrid.grid_line_color = "white" plot_graph.xgrid.grid_line_dash = [6, 4] plot_graph.xgrid.grid_line_alpha = .3 plot_graph.xaxis.axis_line_color = "white" plot_graph.xaxis.axis_label_text_color = "white" plot_graph.xaxis.major_label_text_color = "white" plot_graph.xaxis.major_tick_line_color = "white" plot_graph.xaxis.minor_tick_line_color = "white" plot_graph.xaxis.formatter = DatetimeTickFormatter(microseconds=["%b '%y"], milliseconds=["%b '%y"], seconds=["%b '%y"], minsec=["%b '%y"], minutes=["%b '%y"], hourmin=["%b '%y"], hours=["%b '%y"], days=["%b '%y"], months=["%b '%y"], years=["%b '%y"]) # Y-axis related formatting plot_graph.ygrid.grid_line_color = "white" plot_graph.ygrid.grid_line_dash = [6, 4] plot_graph.ygrid.grid_line_alpha = .3 plot_graph.yaxis.axis_line_color = "white" plot_graph.yaxis.axis_label_text_color = "white" plot_graph.yaxis.major_label_text_color = "white" plot_graph.yaxis.major_tick_line_color = "white" plot_graph.yaxis.minor_tick_line_color = "white" # Graph related Formatting plot_graph.min_border_left = 80 plot_graph.title.text = graph_title plot_graph.title.text_color = "white" plot_graph.title.text_font = "times" plot_graph.title.text_font_style = "normal" plot_graph.title.text_font_size = "14pt" plot_graph.title.align = "center" plot_graph.background_fill_color = '#2F2F2F' plot_graph.border_fill_color = '#2F2F2F' plot_graph.outline_line_color = '#444444' return plot_graph
def main(ini_path, figure_show_flag=False, figure_save_flag=True, figure_size=(1000, 300), start_date=None, end_date=None, crop_str=''): """Plot daily average, median, quantile data for all years by crop Args: ini_path (str): file path of the project INI file figure_show_flag (bool): if True, show figures figure_save_flag (bool): if True, save figures figure_size (tuple): width, height of figure in pixels start_date (str): ISO format date string (YYYY-MM-DD) end_date (str): ISO format date string (YYYY-MM-DD) crop_str (str): comma separate list or range of crops to compare Returns: None """ # Input/output names # input_folder = 'daily_stats' # output_folder = 'daily_plots' # Only process a subset of the crops crop_keep_list = list(util.parse_int_set(crop_str)) # These crops will not be processed (if set) crop_skip_list = [44, 45, 46] # Input field names date_field = 'Date' doy_field = 'DOY' year_field = 'Year' # month_field = 'Month' # day_field = 'Day' # pmeto_field = 'PMETo' precip_field = 'PPT' # t30_field = 'T30' etact_field = 'ETact' etpot_field = 'ETpot' etbas_field = 'ETbas' irrig_field = 'Irrigation' season_field = 'Season' runoff_field = 'Runoff' dperc_field = 'DPerc' # niwr_field = 'NIWR' # Number of header lines in data file # header_lines = 2 # Additional figure controls # figure_dynamic_size = False figure_ylabel_size = '12pt' # Delimiter sep = ',' # sep = r"\s*" # sub_x_range_flag = True logging.info('\nPlot mean daily data by crop') logging.info(' INI: {}'.format(ini_path)) # Check that the INI file can be read crop_et_sec = 'CROP_ET' config = util.read_ini(ini_path, crop_et_sec) # Get the project workspace and daily ET folder from the INI file try: project_ws = config.get(crop_et_sec, 'project_folder') except: logging.error( 'ERROR: The project_folder ' + 'parameter is not set in the INI file') sys.exit() try: input_ws = os.path.join( project_ws, config.get(crop_et_sec, 'daily_output_folder')) except: logging.error( 'ERROR: The daily_output_folder ' + 'parameter is not set in the INI file') sys.exit() try: output_ws = os.path.join( project_ws, config.get(crop_et_sec, 'daily_plots_folder')) except: if 'stats' in input_ws: output_ws = input_ws.replace('stats', 'plots') else: output_ws = os.path.join(project_ws, 'daily_stats_folder') # Check workspaces if not os.path.isdir(input_ws): logging.error(('\nERROR: The input ET folder {0} ' + 'could be found\n').format(input_ws)) sys.exit() if not os.path.isdir(output_ws): os.mkdir(output_ws) # Range of data to plot try: year_start = dt.datetime.strptime(start_date, '%Y-%m-%d').year logging.info(' Start Year: {0}'.format(year_start)) except: year_start = None try: year_end = dt.datetime.strptime(end_date, '%Y-%m-%d').year logging.info(' End Year: {0}'.format(year_end)) except: year_end = None if year_start and year_end and year_end < year_start: logging.error('\n ERROR: End date must be after start date\n') sys.exit() # # Windows only a # if figure_dynamic_size: # : # logging.info('Setting plots width/height dynamically') # from win32api import GetSystemMetrics # figure_width = int(0.92 * GetSystemMetrics(0)) # figure_height = int(0.28 * GetSystemMetrics(1)) # logging.info(' {0} {1}'.format(GetSystemMetrics(0), # GetSystemMetrics(1))) # logging.info(' {0} {1}'.format(figure_width, figure_height)) # : # figure_width = 1200 # figure_height = 300 # Regular expressions data_re = re.compile('(?P<CELLID>\w+)_crop_(?P<CROP>\d+).csv$', re.I) # data_re = re.compile('(?P<CELLID>\w+)_daily_crop_(?P<CROP>\d+).csv$', # re.I) # Build list of all data files data_file_list = sorted( [os.path.join(input_ws, f_name) for f_name in os.listdir(input_ws) if data_re.match(f_name)]) if not data_file_list: logging.error( ' ERROR: No daily ET files were found\n' + ' ERROR: Check the folder_name parameters\n') sys.exit() # Process each file for file_path in data_file_list: file_name = os.path.basename(file_path) logging.debug('') logging.info(' {0}'.format(file_name)) # station, crop_num = os.path.splitext(file_name)[0].split( # '_daily_crop_') station, crop_num = os.path.splitext(file_name)[0].split('_crop_') crop_num = int(crop_num) logging.debug(' Station: {0}'.format(station)) logging.debug(' Crop Num: {0}'.format(crop_num)) if station == 'temp': logging.debug(' Skipping') continue elif crop_skip_list and crop_num in crop_skip_list: logging.debug(' Skipping, crop number in crop_skip_list') continue elif crop_keep_list and crop_num not in crop_keep_list: logging.debug(' Skipping, crop number not in crop_keep_list') continue # Get crop name with open(file_path, 'r') as file_f: crop_name = file_f.readline().split('-', 1)[1].strip() logging.debug(' Crop: {0}'.format(crop_name)) # Read data from file into record array (structured array) daily_df = pd.read_csv(file_path, header=0, comment='#', sep=sep) logging.debug(' Fields: {0}'.format( ', '.join(daily_df.columns.values))) daily_df[date_field] = pd.to_datetime(daily_df[date_field]) daily_df.set_index(date_field, inplace=True) daily_df[year_field] = daily_df.index.year # daily_df[year_field] = daily_df[date_field].map(lambda x: x.year) # Get PMET type from fieldnames in daily .csv field_names = daily_df.columns PMET_str = field_names[4] # if 'PMETr' in field_names: # PMET_str='PMETr' # else: # PMET_str='PMETo' # Build list of unique years year_array = np.sort(np.unique( np.array(daily_df[year_field]).astype(np.int))) logging.debug(' All Years: {0}'.format( ', '.join(list(util.ranges(year_array.tolist()))))) # logging.debug(' All Years: {0}'.format( # ','.join(map(str, year_array.tolist())))) # Don't include the first year in the stats crop_year_start = min(daily_df[year_field]) logging.debug(' Skipping {}, first year'.format(crop_year_start)) daily_df = daily_df[daily_df[year_field] > crop_year_start] # Check if start and end years have >= 365 days crop_year_start = min(daily_df[year_field]) crop_year_end = max(daily_df[year_field]) if sum(daily_df[year_field] == crop_year_start) < 365: logging.debug( ' Skipping {}, missing days'.format(crop_year_start)) daily_df = daily_df[daily_df[year_field] > crop_year_start] if sum(daily_df[year_field] == crop_year_end) < 365: logging.debug( ' Skipping {}, missing days'.format(crop_year_end)) daily_df = daily_df[daily_df[year_field] < crop_year_end] # Only keep years between year_start and year_end # Adjust crop years if year_start: daily_df = daily_df[daily_df[year_field] >= year_start] crop_year_start = max(year_start, crop_year_start) if year_end: daily_df = daily_df[daily_df[year_field] <= year_end] crop_year_end = min(year_end, crop_year_end) year_sub_array = np.sort( np.unique(np.array(daily_df[year_field]).astype(np.int))) logging.debug(' Plot Years: {0}'.format( ', '.join(list(util.ranges(year_sub_array.tolist()))))) # Build separate arrays for each field of non-crop specific data dt_array = daily_df.index.date doy_array = daily_df[doy_field].values.astype(np.int) pmet_array = daily_df[PMET_str].values precip_array = daily_df[precip_field].values # Remove leap days # leap_array = (doy_array == 366) # doy_sub_array = np.delete(doy_array, np.where(leap_array)[0]) # Build separate arrays for each set of crop specific fields etact_array = daily_df[etact_field].values etpot_array = daily_df[etpot_field].values etbas_array = daily_df[etbas_field].values irrig_array = daily_df[irrig_field].values season_array = daily_df[season_field].values runoff_array = daily_df[runoff_field].values dperc_array = daily_df[dperc_field].values kc_array = etact_array / pmet_array kcb_array = etbas_array / pmet_array # build dataframes for grouby input doy_df = pd.DataFrame(doy_array) pmet_df = pd.DataFrame(pmet_array.transpose()) etact_df = pd.DataFrame(etact_array.transpose()) # etpot_df = pd.DataFrame(etpot_array.transpose()) # etbas_df = pd.DataFrame(etbas_array.transpose()) # irrig_df = pd.DataFrame(irrig_array.transpose()) season_df = pd.DataFrame(season_array.transpose()) # runoff_df = pd.DataFrame(runoff_array.transpose()) # dperc_df = pd.DataFrame(dperc_array.transpose()) kc_df = pd.DataFrame(kc_array.transpose()) kcb_df = pd.DataFrame(kcb_array.transpose()) # groupby stats # doy_mean = doy_df[0].groupby(doy_df[0]).mean().values season_median = season_df[0].groupby(doy_df[0]).median().values kc_median = kc_df[0].groupby(doy_df[0]).median().values kcb_median = kcb_df[0].groupby(doy_df[0]).median().values # etbas_median = etbas_df[0].groupby(doy_df[0]).median().values pmet_median = pmet_df[0].groupby(doy_df[0]).median().values # etpot_median = etpot_df[0].groupby(doy_df[0]).median().values etact_median = etact_df[0].groupby(doy_df[0]).median().values # 25% and 75% Percentiles of all years kc_q25 = kc_df[0].groupby(doy_df[0]).quantile(0.25).values kcb_q25 = kcb_df[0].groupby(doy_df[0]).quantile(0.25).values # etbas_q25 = etbas_df[0].groupby(doy_df[0]).quantile(0.25).values # pmet_q25 = pmet_df[0].groupby(doy_df[0]).quantile(0.25).values # etpot_q25 = etpot_df[0].groupby(doy_df[0]).quantile(0.25).values etact_q25 = etact_df[0].groupby(doy_df[0]).quantile(0.25).values kc_q75 = kc_df[0].groupby(doy_df[0]).quantile(0.75).values kcb_q75 = kcb_df[0].groupby(doy_df[0]).quantile(0.75).values # etbas_q75 = etbas_df[0].groupby(doy_df[0]).quantile(0.75).values # etpot_q75 = etpot_df[0].groupby(doy_df[0]).quantile(0.75).values etact_q75 = etact_df[0].groupby(doy_df[0]).quantile(0.75).values # pmet_q75 = pmet_df[0].groupby(doy_df[0]).quantile(0.75).values # NIWR is ET - precip + runoff + deep percolation # Don't include deep percolation when irrigating # niwr_array = etact_array - (precip_array - runoff_array) # niwr_array[irrig_array==0] += dperc_array[irrig_array == 0] # Remove leap days # etact_sub_array = np.delete(etact_array, np.where(leap_array)[0]) # niwr_sub_array = np.delete(niwr_array, np.where(leap_array)[0]) # Manually create date range array for display of month/day on x-axis x_range = Range1d(dt.datetime(2000, 1, 1), dt.datetime(2000, 12, 31)) np_x_range = np.arange(dt.datetime(2000, 1, 1), dt.datetime(2001, 1, 1), dt.timedelta(days=1)).astype(dt.datetime) # Timeseries figures of daily data output_name = '{0}_crop_{1:02d}_avg'.format( station, int(crop_num), crop_year_start, crop_year_end) output_path = os.path.join(output_ws, output_name + '.html') f = output_file(output_path, title=output_name) TOOLS = 'xpan,xwheel_zoom,box_zoom,reset,save' f1 = figure(x_axis_type='datetime', x_range=x_range, width=figure_size[0], height=figure_size[1], tools=TOOLS, toolbar_location="right", active_scroll="xwheel_zoom") # title='Evapotranspiration', x_axis_type='datetime', # if refet_type == 'ETo': f1.line(np_x_range, etact_median, color='blue', legend='ETact Median') f1.line(np_x_range, etact_q75, color='red', legend='ETact 75th percentile') f1.line(np_x_range, etact_q25, color='green', legend='ETact 25th percentile') f1.line(np_x_range, pmet_median, color='black', legend=PMET_str+' Median', line_dash="dashed") # else: # f1.line(np_x_range, et_median, color='blue', # legend='ETr Median') # f1.line(np_x_range, etbas_q75, color='red', # legend='ETr 75th percentile') # f1.line(np_x_range, etpot_q25, color='green', # legend='ETr 25th percentile') # # line_dash="dashdot") # f1.title = 'Evapotranspiration [mm]' f1.grid.grid_line_alpha = 0.3 f1.yaxis.axis_label = 'Evapotranspiration [mm]' f1.yaxis.axis_label_text_font_size = figure_ylabel_size f1.xaxis.formatter = DatetimeTickFormatter(years=['%m/%d'], months=['%m/%d'], days=['%m/%d']) # f1.xaxis.bounds = x_bounds f2 = figure(x_axis_type='datetime', x_range=x_range, width=figure_size[0], height=figure_size[1], tools=TOOLS, toolbar_location="right", active_scroll="xwheel_zoom") f2.line(np_x_range, kc_median, color='blue', legend='Kc Median') f2.line(np_x_range, kc_q75, color='red', legend='Kc 75th percentile') f2.line(np_x_range, kc_q25, color='green', legend='Kc 25th percentile') f2.line(np_x_range, season_median, color='black', legend='Season Median', line_dash="dashed") # f2.title = 'Kc and Kcb (dimensionless)' f2.grid.grid_line_alpha = 0.3 f2.yaxis.axis_label = 'Kc (dimensionless)' f2.yaxis.axis_label_text_font_size = figure_ylabel_size f2.xaxis.formatter = DatetimeTickFormatter(years=['%m/%d'], months=['%m/%d'], days=['%m/%d']) f3 = figure(x_axis_type='datetime', x_range=x_range, width=figure_size[0], height=figure_size[1], tools=TOOLS, toolbar_location="right", active_scroll="xwheel_zoom") f3.line(np_x_range, kcb_median, color='blue', legend='Kcb Median') f3.line(np_x_range, kcb_q75, color='red', legend='Kcb 75th percentile') f3.line(np_x_range, kcb_q25, color='green', legend='Kcb 25th percentile') f3.line(np_x_range, season_median, color='black', legend='Season Median', line_dash="dashed") # f3.title = 'PPT and Irrigation [mm]' f3.grid.grid_line_alpha = 0.3 # f3.xaxis.axis_label = 'Day of Year' f3.xaxis.axis_label_text_font_size = figure_ylabel_size f3.yaxis.axis_label = 'Kcb (dimensionless)' f3.yaxis.axis_label_text_font_size = figure_ylabel_size f3.xaxis.formatter = DatetimeTickFormatter(years=['%m/%d'], months=['%m/%d'], days=['%m/%d']) if figure_show_flag: # Open in a browser show(column([f1, f2, f3], sizing_mode='stretch_both')) # show(vplot(f1, f2, f3)) if figure_save_flag: save(column([f1, f2, f3], sizing_mode='stretch_both')) # save(vplot(f1, f2, f3)) del f1, f2, f3, f # Cleanup del etact_array, etpot_array, etbas_array del irrig_array, season_array del runoff_array, dperc_array del kc_array, kcb_array # del niwr_array # del etact_sub_array, niwr_sub_array # Cleanup del file_path, daily_df del dt_array, year_array, year_sub_array, doy_array del pmet_array del precip_array gc.collect()
def plot2html(df, title, interval, log, line, ma): df = df.iloc[1:] up = df.close > df.open dn = df.open > df.close kwargs = dict(title=title, tools='xpan, xwheel_zoom, box_zoom, reset', active_drag='box_zoom') if log: kwargs['y_axis_type'] = 'log' plot = figure(x_axis_type='datetime', sizing_mode='stretch_both', **kwargs) plot.toolbar.active_scroll = plot.select_one(WheelZoomTool) plot.background_fill_alpha = 0 plot.border_fill_alpha = 0 plot.grid.grid_line_alpha = 0.2 plot.outline_line_alpha = 0.4 plot.xaxis.axis_line_color = 'whitesmoke' plot.yaxis.axis_line_color = 'whitesmoke' plot.xaxis.axis_line_alpha = 0 plot.yaxis.axis_line_alpha = 0 plot.xaxis.major_tick_line_alpha = 0 plot.yaxis.major_tick_line_alpha = 0 plot.xaxis.minor_tick_line_alpha = 0 plot.yaxis.minor_tick_line_alpha = 0 dtf = DatetimeTickFormatter() dtf.milliseconds = ['%T'] dtf.seconds = dtf.minsec = ['%T'] dtf.hours = dtf.hourmin = dtf.minutes = ['%R'] dtf.days = ['%F'] dtf.months = ['%F'] dtf.years = ['%F'] plot.xaxis.formatter = dtf if line: plot.line(df.time, df.close, color='#55bb77') else: pass if ma: for i, n in enumerate([50, 200]): dma = df.close.rolling(n).mean() col = bokeh.palettes.Category20[20][i % 20] plot.line(df.time, dma, color=col, legend_label='MA-%i' % n) plot.legend.background_fill_color = '#222222' plot.legend.background_fill_alpha = 0.6 plot.legend.label_text_color = 'whitesmoke' if not line: totime = { '1m': 60, '5m': 5 * 60, '15m': 15 * 60, '30m': 30 * 60, '1h': 60 * 60, '2h': 2 * 60 * 60, '4h': 4 * 60 * 60, '6h': 6 * 60 * 60, '12h': 12 * 60 * 60, '1d': 24 * 60 * 60, '1w': 7 * 24 * 60 * 60 } w = totime[interval] * 0.7 * 1000 plot.segment(df.time[up], df.hi[up], df.time[up], df.lo[up], color='#33dd99') plot.segment(df.time[dn], df.hi[dn], df.time[dn], df.lo[dn], color='#ff8866') plot.vbar(df.time[up], w, df.open[up], df.close[up], fill_color='#558866', line_color='#33dd99') plot.vbar(df.time[dn], w, df.open[dn], df.close[dn], fill_color='#cc9988', line_color='#ff8866') script, div = plot_html(plot) return div + script
def app(doc, hist_storage_, data_storage_, freq_storage_, depolarizer, names): # вспомогательные глобальные data_source = ColumnDataSource({key: [] for key in names}) fit_handler = { "fit_line": None, "input_fields": {}, "fit_indices": tuple() } utc_plus_7h = 7 * 3600 time_coef = 10**3 # Пересчёт времени в мс для формата datetime Bokeh fit_line_points_amount = 300 # Количество точек для отрисовки подгоночной кривой depol_list = [] datetime_formatter = DatetimeTickFormatter( milliseconds=['%M:%S:%3Nms'], seconds=['%H:%M:%S'], minsec=['%H:%M:%S'], minutes=['%H:%M:%S'], hourmin=['%H:%M:%S'], hours=['%H:%M:%S'], days=["%d.%m"], months=["%Y-%m-%d"], ) # Гистограмма пятна img, img_x_std, img_y_std = hist_storage_.get_hist_with_std() hist_source = ColumnDataSource(data=dict(image=[img])) width_ = config.GEM_X * 5 hist_height_ = config.GEM_Y * 5 hist_fig = figure(plot_width=width_, plot_height=hist_height_, x_range=(0, config.GEM_X), y_range=(0, config.GEM_Y)) hist_fig.image(image='image', x=0, y=0, dw=config.GEM_X, dh=config.GEM_Y, palette="Spectral11", source=hist_source) hist_label = Label( x=0, y=0, x_units='screen', y_units='screen', text=f"x_std={'%.2f' % img_x_std},y_std={'%.2f' % img_y_std}", render_mode='css', border_line_color='black', border_line_alpha=1.0, background_fill_color='white', background_fill_alpha=1.0) hist_fig.add_layout(hist_label) hist_buffer_len = config.hist_buffer_len - 1 hist_slider = RangeSlider(start=0, end=hist_buffer_len, value=(0, hist_buffer_len), step=1, title="Срез пятна (от..до) сек назад") def hist_update(): img, img_x_std, img_y_std = hist_storage_.get_hist_with_std( hist_buffer_len - hist_slider.value[1], hist_buffer_len - hist_slider.value[0]) hist_label.text = f"x_std={'%.2f' % img_x_std},y_std={'%.2f' % img_y_std}" hist_source.data = {'image': [img]} # График асимметрии asym_fig = figure( plot_width=width_, plot_height=400, tools="box_zoom, xbox_select, wheel_zoom, pan, save, reset", active_scroll="wheel_zoom", active_drag="pan", toolbar_location="below", lod_threshold=100, x_axis_location=None, x_range=DataRange1d()) asym_fig.yaxis.axis_label = "мм" asym_fig.extra_x_ranges = { "time_range": asym_fig.x_range, "depolarizer": asym_fig.x_range, "sec": asym_fig.x_range } depol_axis = LinearAxis(x_range_name="depolarizer", axis_label='Деполяризатор', major_label_overrides={}, major_label_orientation=pi / 2) asym_fig.add_layout( LinearAxis(x_range_name="time_range", axis_label='Время', formatter=datetime_formatter), 'below') # Прямая, с которой идёт отсчёт времени для подгонки zone_of_interest = Span(location=0, dimension='height', line_color='green', line_dash='dashed', line_width=3) sec_axis = LinearAxis( x_range_name='sec', axis_label='Секунды') # Секундная ось сверху (настр. диапазон) sec_axis.formatter = FuncTickFormatter( code= f"return ((tick - {zone_of_interest.location}) / {time_coef}).toFixed(1);" ) def double_tap(event): """Двойной клик для перемещения отсчёта времени для подгонки""" zone_of_interest.location = event.x sec_axis.formatter = FuncTickFormatter( code=f"return ((tick - {event.x}) / {time_coef}).toFixed(1);") asym_fig.add_layout(depol_axis, 'below') asym_fig.add_layout(sec_axis, 'above') asym_fig.add_layout(zone_of_interest) asym_fig.on_event(DoubleTap, double_tap) def draw_selected_area(attr, old, new): """Подсветка выделенной для подгонки области""" # Удаляет предыдущую выделенную область asym_fig.renderers = [ r for r in asym_fig.renderers if r.name != 'fit_zone' ] if not new.indices: fit_handler["fit_indices"] = tuple() return l_time_ = data_source.data['time'][min(new.indices)] r_time_ = data_source.data['time'][max(new.indices)] if l_time_ != r_time_: fit_handler["fit_indices"] = (l_time_, r_time_) box_select = BoxAnnotation(left=l_time_, right=r_time_, name="fit_zone", fill_alpha=0.1, fill_color='red') asym_fig.add_layout(box_select) asym_box_select_overlay = asym_fig.select_one(BoxSelectTool).overlay asym_box_select_overlay.line_color = "firebrick" data_source.on_change('selected', draw_selected_area) def create_whisker(data_name: str): """ Создает усы для data_name от time :param data_name: имя поля данных из data_storage (у данных должны быть поля '_up_error', '_down_error') :return: Bokeh Whisker """ return Whisker(source=data_source, base="time", upper=data_name + "_up_error", lower=data_name + "_down_error") def create_render(data_name: str, glyph: str, color: str): """ Рисует data_name от time :param data_name: имя поля данных из data_storage :param glyph: ['circle', 'square'] :param color: цвет :return: Bokeh fig """ if glyph == 'circle': func = asym_fig.circle elif glyph == 'square': func = asym_fig.square else: raise ValueError('Неверное значение glyph') return func('time', data_name, source=data_source, name=data_name, color=color, nonselection_alpha=1, nonselection_color=color) # Список линий на графике асимметрии: data_name, name, glyph, color asym_renders_name = [('y_one_asym', 'ΔY ONE', 'circle', 'black'), ('y_cog_asym', 'ΔY COG', 'circle', 'green'), ('x_one_asym', 'ΔX ONE', 'square', 'black'), ('x_cog_asym', 'ΔX COG', 'square', 'green')] pretty_names = dict([(data_name, name) for data_name, name, *_ in asym_renders_name]) asym_renders = [ create_render(data_name, glyph, color) for data_name, _, glyph, color in asym_renders_name ] asym_error_renders = [ create_whisker(data_name) for data_name, *_ in asym_renders_name ] for render, render_error in zip(asym_renders, asym_error_renders): asym_fig.add_layout(render_error) render.js_on_change( 'visible', CustomJS(args=dict(x=render_error), code="x.visible = cb_obj.visible")) asym_fig.add_layout( Legend(items=[(pretty_names[r.name], [r]) for r in asym_renders], click_policy="hide", location="top_left", background_fill_alpha=0.2, orientation="horizontal")) # Вывод информации о точке при наведении мыши asym_fig.add_tools( HoverTool( renderers=asym_renders, formatters={"time": "datetime"}, mode='vline', tooltips=[ ("Время", "@time{%F %T}"), *[(pretty_names[r.name], f"@{r.name}{'{0.000}'} ± @{r.name + '_error'}{'{0.000}'}") for r in asym_renders], ("Деполяризатор", f"@depol_energy{'{0.000}'}") ])) # Окно ввода периода усреднения period_input = TextInput(value='300', title="Время усреднения (с):") # Глобальный список параметров, для сохранения результатов запросов к data_storage params = {'last_time': 0, 'period': int(period_input.value)} def update_data(): """ Обновляет данные для пользовательского интерфейса, собирая их у data_storage """ if params['period'] != int(period_input.value): data_source.data = {name: [] for name in names} params['period'] = int(period_input.value) params['last_time'] = 0 depol_axis.ticker = [] depol_axis.major_label_overrides.clear() depol_list.clear() points, params['last_time'] = data_storage_.get_mean_from( params['last_time'], params['period']) if not points['time']: return points['time'] = [(i + utc_plus_7h) * time_coef for i in points['time'] ] # Учёт сдвижки UTC+7 для отрисовки for time, energy in zip(points['time'], points['depol_energy']): if energy == 0: continue depol_axis.major_label_overrides[time] = str(energy) depol_list.append(time) depol_axis.ticker = depol_list # TODO: оптимизировать data_source.stream({key: np.array(val) for key, val in points.items()}, rollover=250) def period_correction_func(attr, old, new): """Проверка введенного значения на целое число больше нуля""" if not new.isdigit() or int(new) <= 0: period_input.value = old period_input.on_change('value', period_correction_func) # Создание панели графиков (вкладок) def create_fig(data_names: list, colors: list, y_axis_name: str, ers: str = None): """Создаёт график data_names : time. Если в data_names несколько имён, то они будут на одном графике. Возвращает fig. :param data_names: список с именами полей данных из data_storage :param colors: список цветов, соотв. элементам из fig_names :param y_axis_name: имя оси Y :param ers: 'err', 'pretty' --- вид усов (у данных должны быть поля '_up_error', '_down_error'), 'err' --- усы обыкновенные 'pretty' --- усы без шляпки и цветом совпадающим с цветом точки :return fig --- Bokeh figure """ if len(data_names) != len(colors): raise IndexError('Кол-во цветов и графиков не совпадает') fig = figure(plot_width=width_, plot_height=300, tools="box_zoom, wheel_zoom, pan, save, reset", active_scroll="wheel_zoom", lod_threshold=100, x_axis_type="datetime") for fig_name, color in zip(data_names, colors): if ers == 'err': fig.add_layout( Whisker(source=data_source, base="time", upper=fig_name + '_up_error', lower=fig_name + '_down_error')) elif ers == 'pretty': fig.add_layout( Whisker(source=data_source, base="time", upper=fig_name + '_up_error', lower=fig_name + '_down_error', line_color=color, lower_head=None, upper_head=None)) fig.circle('time', fig_name, source=data_source, size=5, color=color, nonselection_alpha=1, nonselection_color=color) fig.yaxis.axis_label = y_axis_name fig.xaxis.axis_label = 'Время' fig.xaxis.formatter = datetime_formatter fig.x_range = asym_fig.x_range return fig figs = [(create_fig(['y_one_l'], ['black'], 'Y [мм]', 'err'), 'Y ONE L'), (create_fig(['y_one_r'], ['black'], 'Y [мм]', 'err'), 'Y ONE R'), (create_fig(['y_cog_l'], ['black'], 'Y [мм]', 'err'), 'Y COG L'), (create_fig(['y_cog_r'], ['black'], 'Y [мм]', 'err'), 'Y COG R'), (create_fig(['rate' + i for i in ['_l', '_r']], ['red', 'blue'], 'Усл. ед.', 'pretty'), 'Rate'), (create_fig(['corrected_rate' + i for i in ['_l', '_r']], ['red', 'blue'], 'Усл. ед.', 'pretty'), 'Corr. rate'), (create_fig(['delta_rate'], ['black'], 'Корр. лев. - корр. пр.', 'err'), 'Delta corr. rate'), (create_fig(['charge'], ['blue'], 'Ед.'), 'Charge')] tab_handler = Tabs( tabs=[Panel(child=fig, title=fig_name) for fig, fig_name in figs], width=width_) # Окно статуса деполяризатора depol_status_window = Div(text="Инициализация...", width=500, height=500) depol_start_stop_buttons = RadioButtonGroup( labels=["Старт", "Стоп"], active=(0 if depolarizer.is_scan else 1)) fake_depol_button = Button(label="Деполяризовать", width=200) fake_depol_button.on_click(GEM.depolarize) depol_input_harmonic_number = TextInput(value=str( '%.1f' % depolarizer.harmonic_number), title=f"Номер гармоники", width=150) depol_input_attenuation = TextInput(value=str('%.1f' % depolarizer.attenuation), title=f"Аттенюатор (дБ)", width=150) depol_input_speed = TextInput( value=str(depolarizer.frequency_to_energy(depolarizer.speed, n=0)), title=f"Скорость ({'%.1f' % depolarizer.speed} Гц):", width=150) depol_input_step = TextInput( value=str(depolarizer.frequency_to_energy(depolarizer.step, n=0)), title=f"Шаг ({'%.1f' % depolarizer.step} Гц):", width=150) depol_input_initial = TextInput( value=str(depolarizer.frequency_to_energy(depolarizer.initial)), title=f"Начало ({'%.1f' % depolarizer.initial} Гц):", width=150) depol_input_final = TextInput( value=str(depolarizer.frequency_to_energy(depolarizer.final)), title=f"Конец ({'%.1f' % depolarizer.final} Гц):", width=150) depol_dict = { "speed": (depol_input_speed, depolarizer.set_speed), "step": (depol_input_step, depolarizer.set_step), "initial": (depol_input_initial, depolarizer.set_initial), "final": (depol_input_final, depolarizer.set_final), "harmonic_number": (depol_input_harmonic_number, depolarizer.set_harmonic_number), "attenuation": (depol_input_attenuation, depolarizer.set_attenuation) } def change_value_generator(value_name): """Возвращает callback функцию для параметра value_name деполяризатора""" def change_value(attr, old, new): if float(old) == float(new): return depol_input, depol_set = depol_dict[value_name] depol_current = depolarizer.get_by_name(value_name) try: if value_name in ['harmonic_number', 'attenuation']: new_val = float(new) elif value_name in ['speed', 'step']: new_val = depolarizer.energy_to_frequency(float(new), n=0) else: new_val = depolarizer.energy_to_frequency(float(new)) if depol_current == new_val: return depol_set(new_val) if value_name not in ['harmonic_number', 'attenuation']: name = depol_input.title.split(' ')[0] depol_input.title = name + f" ({'%.1f' % new_val} Гц):" except ValueError as e: if value_name in ['harmonic_number', 'attenuation']: depol_input.value = str(depol_current) elif value_name in ['speed', 'step']: depol_input.value = str( depolarizer.frequency_to_energy(depol_current, n=0)) else: depol_input.value = str( depolarizer.frequency_to_energy(depol_current)) print(e) return change_value depol_input_harmonic_number.on_change( 'value', change_value_generator('harmonic_number')) depol_input_attenuation.on_change('value', change_value_generator("attenuation")) depol_input_speed.on_change('value', change_value_generator("speed")) depol_input_step.on_change('value', change_value_generator("step")) depol_input_initial.on_change('value', change_value_generator("initial")) depol_input_final.on_change('value', change_value_generator("final")) def update_depol_status( ): # TODO: самому пересчитывать начало и конец сканирования по частотам """Обновляет статус деполяризатора, если какое-то значение поменялось другим пользователем""" depol_start_stop_buttons.active = 0 if depolarizer.is_scan else 1 depol_status_window.text = f""" <p>Сканирование: <font color={'"green">включено' if depolarizer.is_scan else '"red">выключено'}</font></p> <p/>Частота {"%.1f" % depolarizer.current_frequency} (Гц)</p> <p/>Энергия {"%.3f" % depolarizer.current_energy} МэВ</p>""" for value_name in ['speed', 'step']: depol_input, _ = depol_dict[value_name] depol_value = depolarizer.frequency_to_energy( depolarizer.get_by_name(value_name), n=0) if float(depol_input.value) != depol_value: depol_input.value = str(depol_value) for value_name in ['initial', 'final']: depol_input, _ = depol_dict[value_name] freq = depolarizer.get_by_name(value_name) energy = depolarizer.frequency_to_energy(freq) if float(depol_input.value) != energy: depol_input.value = str(energy) else: name = depol_input.title.split(' ')[0] depol_input.title = name + f" ({'%.1f' % freq} Гц):" for value_name in ['attenuation', 'harmonic_number']: depol_input, _ = depol_dict[value_name] depol_value = depolarizer.get_by_name(value_name) if float(depol_input.value) != depol_value: depol_input.value = str(int(depol_value)) depol_start_stop_buttons.on_change( "active", lambda attr, old, new: (depolarizer.start_scan() if new == 0 else depolarizer.stop_scan())) # Подгонка fit_line_selection_widget = Select(title="Fitting line:", width=200, value=asym_renders[0].name, options=[(render.name, pretty_names[render.name]) for render in asym_renders]) options = [name for name in fit.function_handler.keys()] if not options: raise IndexError("Пустой function_handler в fit.py") fit_function_selection_widget = Select(title="Fitting function:", value=options[0], options=options, width=200) fit_button = Button(label="FIT", width=200) def make_parameters_table(): """Создание поля ввода данных для подгонки: начальное значение, fix и т.д.""" name = fit_function_selection_widget.value t_width = 10 t_height = 12 rows = [ row(Paragraph(text="name", width=t_width, height=t_height), Paragraph(text="Fix", width=t_width, height=t_height), Paragraph(text="Init value", width=t_width, height=t_height), Paragraph(text="step (error)", width=t_width, height=t_height), Paragraph(text="limits", width=t_width, height=t_height), Paragraph(text="lower_limit", width=t_width, height=t_height), Paragraph(text="upper_limit", width=t_width, height=t_height)) ] fit_handler["input_fields"] = {} for param, value in fit.get_function_params(name): fit_handler["input_fields"][param] = {} fit_handler["input_fields"][param]["fix"] = CheckboxGroup( labels=[""], width=t_width, height=t_height) fit_handler["input_fields"][param]["Init value"] = TextInput( width=t_width, height=t_height, value=str(value)) fit_handler["input_fields"][param]["step (error)"] = TextInput( width=t_width, height=t_height, value='1') fit_handler["input_fields"][param]["limits"] = CheckboxGroup( labels=[""], width=t_width, height=t_height) fit_handler["input_fields"][param]["lower_limit"] = TextInput( width=t_width, height=t_height) fit_handler["input_fields"][param]["upper_limit"] = TextInput( width=t_width, height=t_height) rows.append( row(Paragraph(text=param, width=t_width, height=t_height), fit_handler["input_fields"][param]["fix"], fit_handler["input_fields"][param]["Init value"], fit_handler["input_fields"][param]["step (error)"], fit_handler["input_fields"][param]["limits"], fit_handler["input_fields"][param]["lower_limit"], fit_handler["input_fields"][param]["upper_limit"])) return column(rows) def clear_fit(): """Удаление подогнанной кривой""" if fit_handler["fit_line"] in asym_fig.renderers: asym_fig.renderers.remove(fit_handler["fit_line"]) energy_window = Div(text="Частота: , энергия: ") clear_fit_button = Button(label="Clear", width=200) clear_fit_button.on_click(clear_fit) def fit_callback(): if not fit_handler["fit_indices"]: return name = fit_function_selection_widget.value line_name = fit_line_selection_widget.value left_time_, right_time_ = fit_handler["fit_indices"] left_ind_ = bisect.bisect_left(data_source.data['time'], left_time_) right_ind_ = bisect.bisect_right(data_source.data['time'], right_time_, lo=left_ind_) if left_ind_ == right_ind_: return clear_fit() x_axis = data_source.data['time'][left_ind_:right_ind_] y_axis = data_source.data[line_name][left_ind_:right_ind_] y_errors = data_source.data[line_name + '_up_error'][left_ind_:right_ind_] - y_axis init_vals = { name: float(val["Init value"].value) for name, val in fit_handler["input_fields"].items() } steps = { "error_" + name: float(val["step (error)"].value) for name, val in fit_handler["input_fields"].items() } fix_vals = { "fix_" + name: True for name, val in fit_handler["input_fields"].items() if val["fix"].active } limit_vals = { "limit_" + name: (float(val["lower_limit"].value), float(val["upper_limit"].value)) for name, val in fit_handler["input_fields"].items() if val["limits"].active } kwargs = {} kwargs.update(init_vals) kwargs.update(steps) kwargs.update(fix_vals) kwargs.update(limit_vals) # Предобработка времени, перевод в секунды, вычитание сдвига (для лучшей подгонки) left_ = zone_of_interest.location x_time = x_axis - left_ # Привёл время в интервал от 0 x_time /= time_coef # Перевёл в секунды # Создание точек, которые передадутся в подогнанную функцию с параметрами, # и точек, которые соответсвуют реальным временам на графике (т.е. без смещения к 0) fit_line_real_x_axis = np.linspace(left_time_, right_time_, fit_line_points_amount) fit_line_x_axis = fit_line_real_x_axis - left_ fit_line_x_axis /= time_coef m = fit.create_fit_func(name, x_time, y_axis, y_errors, kwargs) fit.fit(m) params_ = m.get_param_states() for param in params_: fit_handler["input_fields"][ param['name']]["Init value"].value = "%.3f" % param['value'] fit_handler["input_fields"][ param['name']]["step (error)"].value = "%.3f" % param['error'] if param['name'] == "depol_time": freq = freq_storage_.find_closest_freq(param['value'] + left_ / time_coef - utc_plus_7h) freq_error = abs(depolarizer.speed * param['error']) energy = depolarizer.frequency_to_energy( freq) if freq != 0 else 0 energy_error = depolarizer.frequency_to_energy( freq_error, depolarizer._F0, 0) energy_window.text = "<p>Частота: %8.1f +- %.1f Hz,</p> <p>Энергия: %7.3f +- %.1f МэВ</p>" % ( freq, freq_error, energy, energy_error) fit_handler["fit_line"] = asym_fig.line( fit_line_real_x_axis, fit.get_line(name, fit_line_x_axis, [x['value'] for x in params_]), color="red", line_width=2) fit_button.on_click(fit_callback) # Инициализация bokeh app, расположение виджетов column_1 = column(gridplot([tab_handler], [asym_fig], merge_tools=False), period_input, width=width_ + 50) widgets_ = WidgetBox(depol_start_stop_buttons, depol_input_harmonic_number, depol_input_attenuation, depol_input_speed, depol_input_step, depol_input_initial, depol_input_final, depol_status_window) row_21 = column(hist_fig, hist_slider) column_21 = column(widgets_) if config.GEM_idle: column_22 = column(fit_button, clear_fit_button, fake_depol_button, fit_line_selection_widget, fit_function_selection_widget, energy_window, make_parameters_table()) make_parameters_table_id = 6 else: column_22 = column(fit_button, clear_fit_button, fit_line_selection_widget, fit_function_selection_widget, energy_window, make_parameters_table()) make_parameters_table_id = 5 def rebuild_table(attr, old, new): column_22.children[make_parameters_table_id] = make_parameters_table() fit_function_selection_widget.on_change("value", rebuild_table) row_22 = row(column_21, column_22) column_2 = column(row_21, row_22, width=width_ + 50) layout_ = layout([[column_1, column_2]]) # Настройка документа Bokeh update_data() doc.add_periodic_callback(hist_update, 1000) # TODO запихнуть в один callback doc.add_periodic_callback(update_data, 1000) # TODO: подобрать периоды doc.add_periodic_callback(update_depol_status, 1000) doc.title = "Laser polarimeter" doc.add_root(layout_)
def score_plot(session, event): from bokeh.plotting import figure from bokeh.models.sources import ColumnDataSource from bokeh.models.formatters import DatetimeTickFormatter submissions = get_submissions(session, event.name, None) submissions = [ get_submission_by_id(session, sub_id) for sub_id, _, _ in submissions if get_submission_by_id(session, sub_id).is_public_leaderboard and get_submission_by_id(session, sub_id).is_valid ] score_names = [score_type.name for score_type in event.score_types] scoress = np.array([[ score.valid_score_cv_bag for score in submission.ordered_scores(score_names) ] for submission in submissions]).T score_plot_df = pd.DataFrame() score_plot_df['submitted at (UTC)'] = [ submission.submission_timestamp for submission in submissions ] score_plot_df['contributivity'] = [ submission.contributivity for submission in submissions ] score_plot_df['historical contributivity'] = [ submission.historical_contributivity for submission in submissions ] for score_name in score_names: # to make sure the column is created score_plot_df[score_name] = 0 for score_name, scores in zip(score_names, scoress): score_plot_df[score_name] = scores score_name = event.official_score_name score_plot_df = score_plot_df[ score_plot_df['submitted at (UTC)'] > event.opening_timestamp] score_plot_df = score_plot_df.sort_values('submitted at (UTC)') score_plot_df = add_pareto(score_plot_df, score_name, event.official_score_type.worst, event.official_score_type.is_lower_the_better) is_open = (score_plot_df['submitted at (UTC)'] > event.public_opening_timestamp).values max_contributivity = max(0.0000001, max(score_plot_df['contributivity'].values)) max_historical_contributivity = max( 0.0000001, max(score_plot_df['historical contributivity'].values)) fill_color_1 = (176, 23, 31) fill_color_2 = (16, 78, 139) fill_colors_1 = color_gradient( fill_color_1, score_plot_df['contributivity'].values / max_contributivity) fill_colors_2 = color_gradient( fill_color_2, score_plot_df['historical contributivity'].values / max_historical_contributivity) fill_colors = np.minimum(fill_colors_1, fill_colors_2).astype(int) fill_colors = ["#%02x%02x%02x" % (c[0], c[1], c[2]) for c in fill_colors] score_plot_df['x'] = score_plot_df['submitted at (UTC)'] score_plot_df['y'] = score_plot_df[score_name] score_plot_df['line_color'] = 'royalblue' score_plot_df['circle_size'] = 8 score_plot_df['line_color'] = 'royalblue' score_plot_df.loc[is_open, 'line_color'] = 'coral' score_plot_df['fill_color'] = fill_colors score_plot_df['fill_alpha'] = 0.5 score_plot_df['line_width'] = 0 score_plot_df['label'] = 'closed phase' score_plot_df.loc[is_open, 'label'] = 'open phase' source = ColumnDataSource(score_plot_df) pareto_df = score_plot_df[score_plot_df[score_name + ' pareto'] == 1].copy() pareto_df = pareto_df.append(pareto_df.iloc[-1]) pareto_df.iloc[-1, pareto_df.columns.get_loc('x')] = (max(score_plot_df['x'])) pareto_df = make_step_df(pareto_df, event.official_score_type.is_lower_the_better) source_pareto = ColumnDataSource(pareto_df) tools = ['pan,wheel_zoom,box_zoom,reset,previewsave,tap'] p = figure(plot_width=900, plot_height=600, tools=tools, title='Scores') p.circle('x', 'y', size='circle_size', line_color='line_color', fill_color='fill_color', fill_alpha='fill_alpha', line_width=1, source=source, legend='label') p.line('x', 'y', line_width=3, line_color='goldenrod', source=source_pareto, legend='best score', alpha=0.9) p.xaxis.formatter = DatetimeTickFormatter( hours=['%d %B %Y'], days=['%d %B %Y'], months=['%d %B %Y'], years=['%d %B %Y'], ) p.xaxis.major_label_orientation = np.pi / 4 if event.official_score_type.is_lower_the_better: p.yaxis.axis_label = score_name + ' (the lower the better)' p.legend.location = 'top_right' else: p.yaxis.axis_label = score_name + ' (the greater the better)' p.legend.location = 'bottom_right' p.xaxis.axis_label = 'submission timestamp (UTC)' p.xaxis.axis_label_text_font_size = '14pt' p.yaxis.axis_label_text_font_size = '14pt' p.legend.label_text_font_size = '14pt' p.title.text_font_size = '16pt' p.xaxis.major_label_text_font_size = '10pt' p.yaxis.major_label_text_font_size = '10pt' return p
def deceased_cases(): dec_cases_excel = 'https://www.skane.se/globalassets/lagesbild-covid-19-i-skane/avlidna-per-dag.xlsx' apd = pd.read_excel(dec_cases_excel, sheet_name='Blad1', usecols="A:F") apd.rename(columns={'Unnamed: 0': 'Date'}, inplace=True) apd.dropna() date = apd['Date'].copy().tolist() date = fix_dates(date) tickers = fix_mondays(date).astype(int) / 10**6 tot = apd['Totalt antal avlidna i Skåne'].tolist() tot = [int(i) for i in tot] totps = apd['Totalt antal avlidna på sjukhus'].tolist() totps = [int(i) for i in totps] dagps = apd['Dagligt antal avlidna på sjukhus'].tolist() dagps = [int(i) for i in dagps] totus = apd['Totalt antal avlidna utanför sjukhus'].tolist() totus = [int(i) for i in totus] dagus = apd['Dagligt antal avlidna utanför sjukhus'].tolist() dagus = [int(i) for i in dagus] legend_cases = [ 'Dagligt antal avlidna på sjukhus', 'Dagligt antal avlidna utanför sjukhus' ] vart = ['Daps', 'Daus'] colors = ["#5069c8", "#bfc8f6"] data = { 'Date': datetime(date), 'Tot': tot, 'Totps': totps, 'Daps': dagps, 'Totus': totus, 'Daus': dagus } min_x_range = max(date) - timedelta(days=30) max_x_range = max(date) + timedelta(days=1) p = figure(y_range=(0, max(max(totps), max(totus)) * 1.1), title="Avlidna per dag", x_axis_type='datetime', x_range=(min_x_range, max_x_range), plot_width=950, tools="xpan", plot_height=300, x_axis_location="above") p.add_tools( HoverTool(tooltips=[ ('Datum', '@Date{%F}'), ('Totalt antal avlidna i Skåne', '@Tot'), ('Totalt antal avlidna på sjukhus', '@Totps'), ('Totalt antal avlidna utanför sjukhus', '@Totus'), ('Dagligt antal avlidna på sjukhus', '@Daps'), ('Dagligt antal avlidna utanför sjukhus', '@Daus'), ], formatters={'@Date': 'datetime'})) p.xaxis.axis_label = 'Datum' p.xaxis.ticker = FixedTicker(ticks=list(tickers)) p.yaxis.axis_label = 'Totala Fall' p.toolbar_location = None p.grid.grid_line_alpha = 0.3 p.extra_y_ranges["dfall"] = Range1d(start=0, end=max(data['Daps']) + max(data['Daus'])) p.vbar_stack(vart, x='Date', y_range_name="dfall", width=60000000, color=colors, source=data, legend_label=["%s" % x for x in legend_cases]) p.add_layout(LinearAxis(y_range_name="dfall", axis_label="Dagliga Fall"), 'right') p.line(y='Totps', x='Date', color="#5069c8", legend_label="Totalt antal avlidna på sjukhus", source=data) p.line(y='Totus', x='Date', color="#c75650", legend_label="Totalt antal avlidna utanför sjukhus", source=data) p.xaxis.formatter = DatetimeTickFormatter(days=["%Y-%m-%d"]) p.legend.location = "top_left" p.legend.background_fill_alpha = 0.2 p.legend.border_line_alpha = 0.0 select = figure( title="Dra i mitten eller kanterna av rutan för att ändra ovan", plot_height=130, plot_width=950, y_range=p.y_range, x_axis_type="datetime", y_axis_type=None, tools="", toolbar_location=None, background_fill_color="#efefef") range_tool = RangeTool(x_range=p.x_range) range_tool.overlay.fill_color = "navy" range_tool.overlay.fill_alpha = 0.2 select.line('Date', 'Totps', color="#5069c8", source=data) select.line('Date', 'Totus', color="#c75650", source=data) select.ygrid.grid_line_color = None select.add_tools(range_tool) select.toolbar.active_multi = range_tool select.xaxis.formatter = DatetimeTickFormatter(days=["%Y-%m-%d"]) return column(p, select)