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
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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
Exemplo n.º 5
0
    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
Exemplo n.º 7
0
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
Exemplo n.º 8
0
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_)