def generate_plot(plot_df): window_start = plot_df.query(f"{AGE_COL} >= {DEFAULT_WINDOW_SIZE}").index.max() # Creating Main Plot window_total_max = plot_df.loc[plot_df.index > window_start, [STILL_OPEN_COL]].max().max() * 1.1 logging.debug(f"window_start={window_start}, window_total_max={window_total_max}") plot = figure( plot_height=None, plot_width=None, sizing_mode="scale_both", x_range=(-5, DEFAULT_WINDOW_SIZE), y_range=(0, window_total_max), toolbar_location=None, x_axis_label="Request Age (days)", y_axis_label="Open SR Count (#)", background_fill_color="#eaeaf2", ) plot.extra_y_ranges = {"proportion": Range1d(start=0, end=plot_df[CUMULATIVE_PROPORTION_COL].max() * 1.1)} secondary_axis = LinearAxis(y_range_name="proportion", axis_label="Request Proportion (%)") plot.add_layout(secondary_axis, 'right') # Open and less than 180 days vbar_open = plot.vbar( top=STILL_OPEN_COL, x=AGE_COL, width=0.75, source=plot_df.query(f"{AGE_COL} < @MARKER_LOCATION"), fill_color="#4c72b0", line_color="#4c72b0", alpha=0.8, line_alpha=0.8 ) # Open and more than 180 days vbar_still_open = plot.vbar( top=STILL_OPEN_COL, x=AGE_COL, width=0.75, source=plot_df.query(f"{AGE_COL} >= @MARKER_LOCATION"), fill_color="#c44e52", line_color="#c44e52", alpha=0.8, line_alpha=0.8 ) # 180 day Marker line marker_span = Span( location=MARKER_LOCATION, dimension='height', line_color="#dd8452", line_dash='dashed', line_width=4 ) plot.add_layout(marker_span) # Cumulative Proportion Line cum_prop_line = plot.line( y=CUMULATIVE_PROPORTION_COL, x=AGE_COL, width=2, source=plot_df, y_range_name="proportion", line_color="#55a868", alpha=0.8, line_alpha=0.8 ) # Plot grid and axis service_delivery_volume_code_metric_plot_widgets_to_minio.stlye_axes(plot) plot.yaxis.formatter = NumeralTickFormatter(format="0.[0] a") secondary_axis.formatter = NumeralTickFormatter(format="0.[00] %") # Plot legend legend_items = [("Open < 180 days", [vbar_open]), ("Open > 180 days", [vbar_still_open]), ("Open Requests", [cum_prop_line])] service_delivery_volume_code_metric_plot_widgets_to_minio.add_legend(plot, legend_items) # Plot tooltip hover_tool = HoverTool(tooltips=HOVER_COLS, mode="vline", formatters={f'@{DATE_COL}': 'datetime'}, renderers=[cum_prop_line]) plot.add_tools(hover_tool) # Adding select figure below main plot select = figure(plot_height=75, plot_width=None, y_range=plot.y_range, sizing_mode="scale_width", y_axis_type=None, tools="", toolbar_location=None, background_fill_color="#eaeaf2") range_tool = RangeTool(x_range=plot.x_range) range_tool.overlay.fill_color = "navy" range_tool.overlay.fill_alpha = 0.2 select_open = select.line( y=STILL_OPEN_COL, x=AGE_COL, line_width=1, source=plot_df.query(f"{AGE_COL} < @MARKER_LOCATION"), line_color="#4c72b0", alpha=0.6, line_alpha=0.6 ) select_still_open = select.line( y=STILL_OPEN_COL, x=AGE_COL, line_width=1, source=plot_df.query(f"{AGE_COL} >= @MARKER_LOCATION"), line_color="#c44e52", alpha=0.6, line_alpha=0.6 ) select.xgrid.grid_line_color = "White" service_delivery_volume_code_metric_plot_widgets_to_minio.stlye_axes(select) select.ygrid.grid_line_color = None select.add_tools(range_tool) select.toolbar.active_multi = range_tool combined_plot = column(plot, select, height_policy="max", width_policy="max") plot_html = file_html(combined_plot, CDN, "Business Continuity Service Delivery Request Age Distribution") return plot_html
def generate_plot(plot_df): start_date = plot_df[DATE_COL_NAME].max() - pandas.Timedelta(days=28) end_date = plot_df[DATE_COL_NAME].max() + pandas.Timedelta(days=1) logging.debug(f"x axis range: {start_date} - {end_date}") # Main plot line_plot = figure( title=None, plot_height=300, plot_width=None, x_axis_type='datetime', sizing_mode="scale_both", x_range=(start_date, end_date), y_axis_label="Rate (%)", y_range=(0, 1.05), toolbar_location=None, ) # Adding count on the right line_plot.extra_y_ranges = { "count_range": Range1d(start=0, end=plot_df[DAY_COUNT_COL].max() * 1.1) } second_y_axis = LinearAxis(y_range_name="count_range", axis_label="Staff Assessed (#)") line_plot.add_layout(second_y_axis, 'right') # Bar plot for counts count_vbar = line_plot.vbar(x=DATE_COL_NAME, top=DAY_COUNT_COL, width=5e7, color="blue", source=plot_df, y_range_name="count_range", alpha=0.4) # Line plots absent_line = line_plot.line(x=DATE_COL_NAME, y=ABSENTEEISM_RATE_COL, color='red', source=plot_df, line_width=5) absent_scatter = line_plot.scatter(x=DATE_COL_NAME, y=ABSENTEEISM_RATE_COL, fill_color='red', source=plot_df, size=12, line_alpha=0) covid_line = line_plot.line(x=DATE_COL_NAME, y=COVID_SICK_COL, color='orange', source=plot_df, line_width=5) covid_scatter = line_plot.scatter(x=DATE_COL_NAME, y=COVID_SICK_COL, fill_color='orange', source=plot_df, size=12, line_alpha=0) # axis formatting line_plot.xaxis.formatter = DatetimeTickFormatter(days="%Y-%m-%d") line_plot.xaxis.major_label_orientation = math.pi / 4 line_plot.axis.axis_label_text_font_size = "12pt" line_plot.axis.major_label_text_font_size = "12pt" line_plot.yaxis.formatter = NumeralTickFormatter(format="0 %") second_y_axis.formatter = NumeralTickFormatter(format="0 a") # Legend legend_items = [ ("Assessed", [count_vbar]), ("Not at Work - 7 day mean (%)", [absent_line, absent_scatter]), ("Covid-19 Exposure - 7 day mean (%)", [covid_line, covid_scatter]) ] legend = Legend(items=legend_items, location="center", orientation="horizontal", padding=2, margin=2) line_plot.add_layout(legend, "below") # Tooltip tooltips = [("Date", "@Date{%F}"), ("Staff Absent", f"@{ABSENTEEISM_RATE_COL}{{0.0 %}}"), ("Covid-19 Exposure", f"@{COVID_SICK_COL}{{0.0 %}}"), ("Staff Assessed", f"@{DAY_COUNT_COL}{{0 a}}")] hover_tool = HoverTool(tooltips=tooltips, mode='vline', renderers=[count_vbar], formatters={'@Date': 'datetime'}) line_plot.add_tools(hover_tool) # Adding select figure below main plot select = figure(plot_height=75, plot_width=None, y_range=line_plot.y_range, sizing_mode="scale_width", x_axis_type="datetime", y_axis_type=None, tools="", toolbar_location=None) select.extra_y_ranges = { "count_range": Range1d(start=0, end=plot_df[DAY_COUNT_COL].max() * 1.1) } second_y_axis = LinearAxis(y_range_name="count_range") select.add_layout(second_y_axis, 'right') range_tool = RangeTool(x_range=line_plot.x_range) range_tool.overlay.fill_color = "navy" range_tool.overlay.fill_alpha = 0.2 select_count_line = select.line(y=DAY_COUNT_COL, x=DATE_COL_NAME, line_width=1, source=plot_df, line_color="blue", alpha=0.6, line_alpha=0.6, y_range_name="count_range") select_absent_line = select.line(y=ABSENTEEISM_RATE_COL, x=DATE_COL_NAME, line_width=1, source=plot_df, line_color="red", alpha=0.6, line_alpha=0.6) select_covid_line = select.line(y=COVID_SICK_COL, x=DATE_COL_NAME, line_width=1, source=plot_df, line_color="orange", alpha=0.6, line_alpha=0.6) select.xgrid.grid_line_color = "White" select.ygrid.grid_line_color = None select.xaxis.formatter = DatetimeTickFormatter(days="%Y-%m-%d") select.add_tools(range_tool) select.toolbar.active_multi = range_tool select.axis.axis_label_text_font_size = "12pt" select.axis.major_label_text_font_size = "12pt" combined_plot = column(line_plot, select, height_policy="max", width_policy="max") plot_html = file_html(combined_plot, CDN, "Business Continuity HR Capacity Time Series") return plot_html
def generate_win_plot(positions_source, year_results): """ Plot number of wins, number of podiums, number of points, win percent, podium percent, and points per race as a percentage of #1's points on one plot. Note, this is different from the regular win plot (no num races, instead points and pts per race). :param positions_source: Positions source :param year_results: Year results :return: Win plot layout """ # TODO maybe even refactor this to use a version in driver, but this also has points and pts/race support # Inspired by driver.generate_win_plot logging.info("Generating win plot") if isinstance(positions_source, dict): return Div(text="") win_source = pd.DataFrame(columns=[ "x", "win_pct", "wins", "win_pct_str", "podium_pct", "podiums", "podium_pct_str", "ppr_pct", "points", "ppr_pct_str" "constructor_name", "wdc_final_standing", "wdc_final_standing_str" ]) wins = 0 podiums = 0 n_races = 0 max_potential_points = 0 max_potential_ppr = year_results[year_results["position"] == 1]["points"].mode().values[0] for idx, row in positions_source.sort_values(by="x").iterrows(): x = row["x"] pos = row["finish_position_int"] if not np.isnan(pos): wins += 1 if pos == 1 else 0 podiums += 1 if 3 >= pos > 0 else 0 points = row["points"] max_potential_points += max_potential_ppr n_races += 1 win_pct = wins / n_races podium_pct = podiums / n_races ppr_pct = points / max_potential_points win_source = win_source.append( { "x": x, "wins": wins, "win_pct": win_pct, "podiums": podiums, "podium_pct": podium_pct, "points": points, "ppr_pct": ppr_pct, "constructor_name": row["constructor_name"], "wdc_final_standing": row["wdc_final_standing"], "wdc_final_standing_str": int_to_ordinal( row["wdc_final_standing"]), "win_pct_str": str(round(100 * win_pct, 1)) + "%", "podium_pct_str": str(round(100 * podium_pct, 1)) + "%", "ppr_pct_str": str(round(100 * ppr_pct, 1)) + "%" }, ignore_index=True) max_podium = win_source["podiums"].max() y_max_range = max_podium if max_podium > 0 else 10 win_plot = figure(title="Wins, Podiums, and Points", y_axis_label="", x_axis_label="Year", tools="pan,xbox_zoom,reset,box_zoom,wheel_zoom,save", y_range=Range1d(0, y_max_range, bounds=(-20, 30))) subtitle = "The points percent is calculated as a percentage of the maximum number of points that driver could " \ "achieve (if he/she won every race)" win_plot.add_layout(Title(text=subtitle, text_font_style="italic"), "above") max_podium_pct = win_source["podium_pct"].max() max_ppr_pct = win_source["ppr_pct"].max() max_points = win_source["points"].max() if max_ppr_pct > max_podium_pct and podiums == 0: k = 10 / max_ppr_pct max_y_pct = max_ppr_pct elif max_ppr_pct > max_podium_pct and max_ppr_pct > 0: k = max_podium / max_ppr_pct max_y_pct = max_ppr_pct elif max_podium_pct > 0: k = max_podium / max_podium_pct max_y_pct = max_podium_pct else: max_y_pct = 1 k = 1 win_source["podium_pct_scaled"] = k * win_source["podium_pct"] win_source["win_pct_scaled"] = k * win_source["win_pct"] win_source["ppr_pct_scaled"] = k * win_source["ppr_pct"] if max_points > 0 and max_podium == 0: k_pts = 10 / max_points elif max_points > 0: k_pts = max_podium / max_points else: k_pts = 1 max_y_pct = 1 win_source["points_scaled"] = k_pts * win_source["points"] if max_points == 0 and max_podium == 0: # Theoretically this case could be handled, but not really useful return Div() # Override the x axis x_min = positions_source["x"].min() - 0.001 x_max = positions_source["x"].max() + 0.001 win_plot.x_range = Range1d(x_min, x_max, bounds=(x_min, x_max)) win_plot.xaxis.ticker = FixedTicker(ticks=positions_source["x"]) win_plot.xaxis.major_label_overrides = { row["x"]: row["roundName"] for idx, row in positions_source.iterrows() } win_plot.xaxis.major_label_orientation = 0.8 * math.pi / 2 win_plot.xaxis.axis_label = "" # Other y axis (%) max_y = win_plot.y_range.end y_range = Range1d(start=0, end=max_y_pct, bounds=(-0.02, 1000)) win_plot.extra_y_ranges = {"percent_range": y_range} axis = LinearAxis(y_range_name="percent_range") axis.formatter = NumeralTickFormatter(format="0.0%") win_plot.add_layout(axis, "right") # Third y axis (points) y_range2 = Range1d(start=0, end=max_y / k, bounds=(-0.02, 1000)) win_plot.extra_y_ranges["points_range"] = y_range2 axis2 = LinearAxis(y_range_name="points_range", axis_label="Points") win_plot.add_layout(axis2, "right") kwargs = { "x": "x", "line_width": 2, "line_alpha": 0.7, "source": win_source, "muted_alpha": 0.01 } wins_line = win_plot.line(y="wins", color="green", **kwargs) win_pct_line = win_plot.line(y="win_pct_scaled", color="green", line_dash="dashed", **kwargs) podiums_line = win_plot.line(y="podiums", color="yellow", **kwargs) podium_pct_line = win_plot.line(y="podium_pct_scaled", color="yellow", line_dash="dashed", **kwargs) points_line = win_plot.line(y="points_scaled", color="white", **kwargs) ppr_pct_line = win_plot.line(y="ppr_pct_scaled", color="white", line_dash="dashed", **kwargs) legend = [ LegendItem(label="Number of Wins", renderers=[wins_line]), LegendItem(label="Win Percentage", renderers=[win_pct_line]), LegendItem(label="Number of Podiums", renderers=[podiums_line]), LegendItem(label="Podium Percentage", renderers=[podium_pct_line]), LegendItem(label="Points", renderers=[points_line]), LegendItem(label="Points Percentage", renderers=[ppr_pct_line]) ] legend = Legend(items=legend, location="top_right", glyph_height=15, spacing=2, inactive_fill_color="gray") win_plot.add_layout(legend, "right") win_plot.legend.click_policy = "mute" win_plot.legend.label_text_font_size = "12pt" # Hover tooltip win_plot.add_tools( HoverTool(show_arrow=False, tooltips=[ ("Number of Wins", "@wins (@win_pct_str)"), ("Number of Podiums", "@podiums (@podium_pct_str)"), ("Points", "@points (@ppr_pct_str of max pts. possible)"), ("Constructor", "@constructor_name"), ("Final Position this year", "@wdc_final_standing_str") ])) # Crosshair tooltip win_plot.add_tools(CrosshairTool(line_color="white", line_alpha=0.6)) return win_plot
def generate_dnf_plot(circuit_years, circuit_results, circuit_races, circuit_id): """ Plots number of races, number of DNFs, and DNF percent for that year on the same plot. (2 different axes). :param circuit_years: Circuit years :param circuit_results: Circuit results :param circuit_races: Circuit races :param circuit_id: Circuit ID :return: Plot layout """ # TODO refactor to use existing method logging.info("Generating dnf plot") if len(circuit_years) == 0: return Div() source = pd.DataFrame(columns=["n_races", "year", "n_drivers", "dnf_pct", "dnfs", "dnf_pct_str", "total_dnf_pct", "total_dnfs", "total_dnf_pct_str"]) n_races = 0 total_dnfs = 0 total_drivers = 0 for year in circuit_years: year_race = circuit_races[circuit_races["year"] == year] if year_race.shape[0] == 0: continue rid = year_race.index.values[0] year_results = circuit_results[circuit_results["raceId"] == rid] num_dnfs = year_results["position"].isna().sum() num_drivers = year_results.shape[0] total_dnfs += num_dnfs total_drivers += num_drivers if num_drivers > 0: dnf_pct = num_dnfs / num_drivers total_dnf_pct = total_dnfs / total_drivers n_races += 1 source = source.append({ "n_races": n_races, "n_drivers": num_drivers, "year": year, "dnf_pct": dnf_pct, "dnfs": num_dnfs, "dnf_pct_str": str(round(100 * dnf_pct, 1)) + "%", "total_dnf_pct": total_dnf_pct, "total_dnfs": total_dnfs, "total_dnf_pct_str": str(round(100 * total_dnf_pct, 1)) + "%", }, ignore_index=True) circuit_name = get_circuit_name(circuit_id) min_year = min(circuit_years) max_year = max(circuit_years) max_drivers = source["n_drivers"].max() if max_drivers == 0: return Div() dnf_plot = figure( title=u"Number of DNFs \u2014 " + circuit_name, y_axis_label="", x_axis_label="Year", x_range=Range1d(min_year, max_year, bounds=(min_year, max_year + 3)), tools="pan,xbox_zoom,xwheel_zoom,reset,box_zoom,wheel_zoom,save", y_range=Range1d(0, max_drivers, bounds=(-1000, 1000)) ) subtitle = 'Year DNFs refers to the number/percent of DNFs for that year, Total DNFs refers to all DNFs up to ' \ 'that point in time' dnf_plot.add_layout(Title(text=subtitle, text_font_style="italic"), "above") max_dnf_pct = max(source["dnf_pct"].max(), source["total_dnf_pct"].max()) if max_dnf_pct > 0: k = max_drivers / max_dnf_pct else: k = 1 source["dnf_pct_scaled"] = k * source["dnf_pct"] source["total_dnf_pct_scaled"] = k * source["total_dnf_pct"] # Other y axis y_range = Range1d(start=0, end=max_dnf_pct, bounds=(-0.02, 1000)) dnf_plot.extra_y_ranges = {"percent_range": y_range} axis = LinearAxis(y_range_name="percent_range") axis.formatter = NumeralTickFormatter(format="0.0%") dnf_plot.add_layout(axis, "right") kwargs = { "x": "year", "line_width": 2, "line_alpha": 0.7, "source": source, "muted_alpha": 0.05 } races_line = dnf_plot.line(y="n_races", color="white", **kwargs) drivers_line = dnf_plot.line(y="n_drivers", color="yellow", **kwargs) dnfs_line = dnf_plot.line(y="dnfs", color="aqua", **kwargs) dnf_pct_line = dnf_plot.line(y="dnf_pct_scaled", color="aqua", line_dash="dashed", **kwargs) total_dnfs_line = dnf_plot.line(y="total_dnfs", color="orange", **kwargs) total_dnf_pct_line = dnf_plot.line(y="total_dnf_pct_scaled", color="orange", line_dash="dashed", **kwargs) legend = [LegendItem(label="Number of Races", renderers=[races_line]), LegendItem(label="Number of Drivers", renderers=[drivers_line]), LegendItem(label="Year DNFs", renderers=[dnfs_line]), LegendItem(label="Year DNF Pct.", renderers=[dnf_pct_line]), LegendItem(label="Total DNFs", renderers=[total_dnfs_line]), LegendItem(label="Total DNF Pct.", renderers=[total_dnf_pct_line])] legend = Legend(items=legend, location="top_right", glyph_height=15, spacing=2, inactive_fill_color="gray") dnf_plot.add_layout(legend, "right") dnf_plot.legend.click_policy = "mute" dnf_plot.legend.label_text_font_size = "12pt" # Hover tooltip dnf_plot.add_tools(HoverTool(show_arrow=False, tooltips=[ ("Number of Races", "@n_races"), ("Number of Drivers", "@n_drivers"), ("Year Num. DNFs", "@dnfs (@dnf_pct_str)"), ("Total Num. DNFs", "@total_dnfs (@total_dnf_pct_str)"), ("Year", "@year"), ])) # Crosshair tooltip dnf_plot.add_tools(CrosshairTool(line_color="white", line_alpha=0.6)) return dnf_plot
def make_plot(self): self.p = figure( x_axis_label="Date", x_axis_type="datetime", y_axis_label="Total Cases and Deaths", width=900, ) self.p.extra_y_ranges = {"ratio_axis": Range1d()} axis = LinearAxis(y_range_name="ratio_axis") axis.formatter = NumeralTickFormatter(format="0 %") self.p.add_layout(axis, "right") colors = Category20_3 self.p.line( source=self.src, x="date", y="cases", line_width=2, color=colors[0], legend_label="Cases", ) self.p.line( source=self.src, x="date", y="deaths", line_width=2, color=colors[1], legend_label="Deaths", ) self.p.line( source=self.src, x="date", y="ratio", line_width=2, y_range_name="ratio_axis", color=colors[2], legend_label="Deaths/Cases", ) self.p.legend.location = "top_left" self.logp = figure( x_axis_label="Date", x_axis_type="datetime", y_axis_label="Total Cases and Deaths", y_axis_type="log", width=900, ) self.logp.extra_y_ranges = {"ratio_axis": Range1d()} logaxis = LogAxis(y_range_name="ratio_axis") logaxis.formatter = NumeralTickFormatter(format="0 %") self.logp.add_layout(logaxis, "right") self.logp.line( source=self.src, x="date", y="cases", line_width=2, color=colors[0], legend_label="Cases", ) self.logp.line( source=self.src, x="date", y="deaths", line_width=2, color=colors[1], legend_label="Deaths", ) self.logp.line( source=self.src, x="date", y="ratio", line_width=2, y_range_name="ratio_axis", color=colors[2], legend_label="Deaths/Cases", ) self.p.legend.location = "top_left"
def generate_sentiment_ts_plot(sentiment_ts_data, sentiment_ts_sample_count): tooltips = [ ("Date", "@date{%F}"), ("Mentions", "@Count"), ("Nett Sentiment (7 day mean)", "@NettSentiment{0.0%}"), ] line_plot = figure(title=None, width=None, height=None, sizing_mode="scale_both", x_range=(sentiment_ts_data["date"].min(), sentiment_ts_data["date"].max()), x_axis_type='datetime', y_axis_label="Nett Sentiment (7 day mean)", tools=[], toolbar_location=None) # Setting range of y range line_plot.y_range = Range1d(-1, 1) # Adding Count range on the right line_plot.extra_y_ranges = { "count_range": Range1d(start=0, end=sentiment_ts_sample_count.max() * 1.1) } secondary_axis = LinearAxis(y_range_name="count_range", axis_label="Mentions") line_plot.add_layout(secondary_axis, 'right') sentiment_count_bars = line_plot.vbar(x="date", top="Count", width=pandas.Timedelta(days=0.75), color="orange", source=sentiment_ts_data, y_range_name="count_range") sentiment_line = line_plot.line(x="date", y="NettSentiment", color="blue", source=sentiment_ts_data, line_width=5) # Long Term sentiment baseline long_term_sentiment_span = Span( location=LONG_TERM_SENTIMENT, name="LongTermSentiment", dimension='width', line_color='red', line_dash='dashed', line_width=3, ) line_plot.add_layout(long_term_sentiment_span) start_timestamp = sentiment_ts_data["date"].min() long_term_sentiment_label = Label(x=start_timestamp, y=LONG_TERM_SENTIMENT, x_offset=-10, y_offset=10, text='Sentiment Baseline', render_mode='css', border_line_color='white', border_line_alpha=0.0, angle=0, angle_units='deg', background_fill_color='white', background_fill_alpha=0.0, text_color='red', text_font_size='12pt') line_plot.add_layout(long_term_sentiment_label) hover_tool = HoverTool(renderers=[sentiment_line, sentiment_count_bars], tooltips=tooltips, formatters={ '@date': 'datetime', 'NettSentiment': 'printf' }) line_plot.add_tools(hover_tool) line_plot.xaxis.formatter = DatetimeTickFormatter(days="%Y-%m-%d") line_plot.yaxis.formatter = NumeralTickFormatter(format="0.[0]%") secondary_axis.formatter = NumeralTickFormatter(format="0.[0] a") line_plot.axis.axis_label_text_font_size = "12pt" line_plot.axis.major_label_text_font_size = "12pt" plot_html = file_html(line_plot, CDN, "Behavioural Sentiment Timeseries") return plot_html
def generate_performance_plot(hds, hcols): aux_cols = ['Model', "Number_of_Drives", "Percent_of_Drives", "Color"] cols = ['Failure_Rate', 'Capacity', 'Interface', 'Cache', 'RPM', 'Price_GB'] data = {} for c in cols: #print('col: ', c) data[c] = hds[c] for c in aux_cols: #print('col: ', c) data[c] = hds[c] max_scale = 1.0 min_scale = 0.0 max_cache = np.max(hds["Cache"]) min_cache = np.min(hds["Cache"]) cache = ( (max_scale - min_scale) /(max_cache - min_cache))*(hds["Cache"] - max_cache) + max_scale max_rpm = np.max(hds["RPM"]) min_rpm = np.min(hds["RPM"]) rpm = ( (max_scale - min_scale) /(max_rpm - min_rpm))*(hds["RPM"] - max_rpm) + max_scale max_interface = np.max(hds["Interface"]) min_interface = np.min(hds["Interface"]) interface = ( (max_scale - min_scale) /(max_interface - min_interface))*(hds["Interface"] - max_interface) + max_scale performance = interface + rpm + cache max_performance = np.max(performance) min_performance = np.min(performance) performance = ( (max_scale - min_scale) /(max_performance - min_performance))*(performance - max_performance) + max_scale max_failure = np.max(hds["Failure_Rate"]) min_failure = np.min(hds["Failure_Rate"]) reliability = ( (max_scale - min_scale) /(max_failure - min_failure))*(hds["Failure_Rate"] - max_failure) + max_scale reliability = 1.0 - reliability max_cost = np.max(hds["Price_GB"]) min_cost = np.min(hds["Price_GB"]) cost = ( (max_scale - min_scale) /(max_cost-min_cost))*(hds["Price_GB"]-max_cost) + max_scale cost = 1.0 - cost slider_start = .5 data["x"] = np.arange(0,len(hds['Model']),1) data["y"] = slider_start * cost + slider_start * performance + slider_start * reliability data["Cost"] = slider_start * cost data["Performance"] = slider_start * performance data["Reliability"] = slider_start * reliability sizes = list(range(6, 24, 4)) groups = pd.cut(hds["Capacity"].values, len(sizes)) sz = [sizes[i] for i in groups.codes] data["Size"] = sz static_data = {} static_data["Cost"] = cost static_data["Performance"] = performance static_data["Reliability"] = reliability _source = ColumnDataSource(data=data) _static_source = ColumnDataSource(data=static_data) title = "Relative Hard Drive Value" plot = figure(title=title, x_axis_location='below', y_axis_location='left', tools=['hover','save']) hover = plot.select(dict(type=HoverTool)) hover.tooltips = [ ("Model ", "@Model"), ("Failure Rate ", "@Failure_Rate"), (hcols["Capacity"], "@Capacity"), (hcols["Interface"], "@Interface"), (hcols["RPM"], "@RPM"), (hcols["Cache"], "@Cache"), (hcols["Price_GB"], "@Price_GB{1.11}") ] p1 = plot.circle('x', 'y', source = _source, size='Size', color="Color")#, line_color="white", alpha=0.6, hover_color='white', hover_alpha=0.5) from bokeh.models import FuncTickFormatter#, FixedTickFormatter label_dict = {} for i, s in enumerate(hds["Model"]): label_dict[i] = s plot.y_range = Range1d(-.1, 3.1) plot.toolbar.logo = None plot.xaxis.visible = False plot.yaxis.visible = False from bokeh.models import SingleIntervalTicker ticker = SingleIntervalTicker(interval=1, num_minor_ticks=0) xaxis = LinearAxis(axis_label="Model", ticker=ticker) #yaxis = LinearAxis()#axis_label="Relative Merit") xaxis.formatter = FuncTickFormatter(code=""" var labels = %s; return labels[tick]; """ % label_dict) xaxis.major_label_orientation = -np.pi/2.7 plot.add_layout(xaxis, 'below') #plot.add_layout(yaxis, 'left') callback1 = CustomJS(args=dict(source=_source, static_source=_static_source), code=""" var data = source.get("data"); var static_data = static_source.get("data"); var f = cb_obj.value y = data['y'] reli = data['Reliability'] perf = data['Performance'] cost = data['Cost'] static_cost = static_data['Cost'] for (i = 0; i < y.length; i++) { cost[i] = f * static_cost[i] y[i] = reli[i] + cost[i] + perf[i] } source.trigger('change'); """) callback2 = CustomJS(args=dict(source=_source, static_source=_static_source), code=""" var data = source.get("data"); var static_data = static_source.get("data"); var f = cb_obj.value y = data['y'] reli = data['Reliability'] static_reli = static_data['Reliability'] perf = data['Performance'] cost = data['Cost'] for (i = 0; i < y.length; i++) { reli[i] = f * static_reli[i] y[i] = reli[i] + cost[i] + perf[i] } source.trigger('change'); """) callback3 = CustomJS(args=dict(source=_source, static_source=_static_source), code=""" var data = source.get("data"); static_data = static_source.get("data"); var f = cb_obj.value y = data['y'] reli = data['Reliability'] perf = data['Performance'] static_perf = static_data['Performance'] cost = data['Cost'] for (i = 0; i < y.length; i++) { perf[i] = f*static_perf[i] y[i] = reli[i] + cost[i] + perf[i] } source.trigger('change'); """) plot.min_border_left = 0 plot.xaxis.axis_line_width = 2 plot.yaxis.axis_line_width = 2 plot.title.text_font_size = '16pt' plot.xaxis.axis_label_text_font_size = "14pt" plot.xaxis.major_label_text_font_size = "14pt" plot.yaxis.axis_label_text_font_size = "14pt" plot.yaxis.major_label_text_font_size = "14pt" plot.ygrid.grid_line_color = None plot.xgrid.grid_line_color = None plot.toolbar.logo = None plot.outline_line_width = 0 plot.outline_line_color = "white" slider1 = Slider(start=0.0, end=1.0, value=slider_start, step=.05, title="Price") slider2 = Slider(start=0.0, end=1.0, value=slider_start, step=.05, title="Reliability") slider3 = Slider(start=0.0, end=1.0, value=slider_start, step=.05, title="Performance") slider1.js_on_change('value', callback1) slider2.js_on_change('value', callback2) slider3.js_on_change('value', callback3) controls = widgetbox([slider1, slider2,slider3], width=200) layout = row(controls, plot) return layout
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_)