Exemple #1
0
def multi_spec_dropdown(id_: str, role: str) -> dcc.Dropdown:
    """Constructs spec dropdown for page 2 composition app.

    Parameters
    ----------
    id_ : str
        html id of the component
    role : str
        specs to include in the figure; one of {'tank', 'healer', 'mdps', 'rdps'}

    Returns
    -------
    dropdown : dcc.Dropdown
        dropdown component with specs of given role
    """
    dropdown = dcc.Dropdown(
        id=id_,
        className="spec-input",
        options=[
            {
                "label": spec["spec_name"].upper() + " " + spec["class_name"].upper(),
                "value": spec["token"],
            }
            for spec in blizzcolors.Specs().specs
            if spec["role"][-3:] in role
        ],
        multi=True,
        placeholder="ALL INCLUDED BY DEFAULT.",
    )
    drop_wrap = html.Div(
        children=[html.Label(role.upper()), dropdown],
    )
    return drop_wrap
Exemple #2
0
 def __init__(self, data, xaxis_type, spec_role, patch):
     self.specs = blizzcolors.Specs()
     self.xaxis_type = xaxis_type
     self.spec_role = spec_role
     self.data = self.normalize_for_role(data, spec_role)
     self.patch = patch
     self.traces = None
Exemple #3
0
 def _sort_annos_by_spec(annotations, keep_role):
     """Given a dictionary of annotations, return those that match role."""
     valid_roles = ["tank", "healer", "mdps", "rdps"]
     specs = blizzcolors.Specs()
     if keep_role not in valid_roles:
         raise ValueError("Spec role invalid. Must be one of: %s")
     keep_annotations = []
     for spec_id, annotation in annotations.items():
         spec_role = specs.get_role(spec_id)
         if spec_role == keep_role:
             keep_annotations.append(annotation)
     return keep_annotations
Exemple #4
0
 def _make_spec_name_annos(self, sorted_summary):
     """Make spec name annotations."""
     annotations = {}
     vertical_offset = self._calculate_vertical_offset()
     num_specs = len(sorted_summary)
     specs = blizzcolors.Specs()
     for index, row in enumerate(list(sorted_summary.values)):
         spec_id = row[0]
         spec_name = specs.get_spec_name(spec_id).upper()
         baseline_y = vertical_offset * (num_specs - index)
         anno = self._make_spec_name_annotation(position_x=0,
                                                position_y=baseline_y,
                                                text=spec_name)
         annotations[spec_id] = anno
     return annotations
Exemple #5
0
 def _construct_traces(self, sorted_summary):
     """Makes line/fill traces of the data distribution."""
     key_levels = list(self.data)
     key_levels_x = [i - 2 for i in list(key_levels)]  # x is 0, key is 2
     vertical_offset = self._calculate_vertical_offset()
     num_specs = len(self.summary)
     specs = blizzcolors.Specs()
     traces = {}
     for index, row in enumerate(list(sorted_summary.values)):
         spec_id = row[0]
         spec_color = "rgba(%d,%d,%d,0.9)" % specs.get_color(spec_id)
         runs = self.data.loc[self.data.index == spec_id].values[0]
         # horizontal baseline for each ridge distribution
         baseline_y = vertical_offset * (num_specs - index)
         baseline = go.Scatter(
             x=key_levels_x,
             y=[baseline_y] * len(key_levels_x),
             line=dict(width=0.5, color="black"),
             hoverinfo="skip",
             mode="lines",
         )
         # the distribution of runs vs key level (the star of the show)
         ridge = go.Scatter(
             x=key_levels_x,
             y=[baseline_y + run for run in runs],
             fill="tozerox",
             fillcolor=spec_color,
             mode="lines",
             line=dict(width=1, color="black", shape="spline"),
             name=specs.get_spec_name(spec_id).upper(),
             text=[f"{y:,}" for y in runs],
             customdata=[i + 2 for i in key_levels_x],
             hovertemplate="KEY LEVEL: +%{customdata}<br>RUNS: %{text}",
         )
         traces[spec_id] = {"ridge": ridge, "baseline": baseline}
     return traces
Exemple #6
0
    def _get_ridge_recolor_pattern(self, keep_role):
        """Generates color list that informs recolor of the traces.

        Given a spec role, recolors all traces not of that role to gray.

        Parameters
        ----------
        keep_role : str
            spec role ('tank', 'healer', 'mdps', 'rdps')

        Returns
        -------
        recolor : list
            list of colors, where color at index i corresponds to trace at
            index i in fig.data
        """
        valid_roles = ["tank", "healer", "mdps", "rdps"]
        keep_role = keep_role.lower()
        if keep_role not in valid_roles:
            raise ValueError("Spec role invalid. Must be one of: %s")
        recolor = []
        specs = blizzcolors.Specs()
        custom_gray = "rgba(0,0,0,0.3)"
        for spec_id, spec_traces in self.traces.items():
            spec_role = specs.get_role(spec_id)
            # reassign color based on spec role
            new_ridge_color = None
            if spec_role == keep_role:
                new_ridge_color = spec_traces["ridge"].fillcolor
            else:
                new_ridge_color = custom_gray
            # keep baseline the original color
            baseline_color = spec_traces["baseline"].line.color
            recolor.append(new_ridge_color)
            recolor.append(baseline_color)
        return recolor
Exemple #7
0
def format_output(result0: pd.DataFrame) -> html.Table:
    """Formats results of the comp search into a data table."""
    # Formatting the results is a massive PITA.
    # There are 40+ columns, and condensing them into something that
    # both fits on the screen and is interpretable is rough.
    # So here is what we do:
    # Find the tanks and condense them into a single column.
    tank_cols = [
        "death_knight_blood",
        "demon_hunter_vengeance",
        "druid_guardian",
        "monk_brewmaster",
        "paladin_protection",
        "warrior_protection",
    ]
    tanks = result0[tank_cols].apply(lambda row: tank_cols[list(row).index(1)],
                                     axis=1)
    # Find the healers and condense them into a single column.
    healer_cols = [
        "druid_restoration",
        "monk_mistweaver",
        "paladin_holy",
        "priest_discipline",
        "priest_holy",
        "shaman_restoration",
    ]
    healers = result0[healer_cols].apply(
        lambda row: healer_cols[list(row).index(1)], axis=1)
    # drop both healers and tanks from the main table
    result0.drop(
        inplace=True,
        labels=tank_cols + healer_cols,
        axis=1,
    )
    # and append the condensed columns
    result0["tank"] = tanks
    result0["healer"] = healers

    # remove stats for now
    stats = result0[["run_count", "level_mean", "level_max"]].copy(deep=True)
    stats = stats.round(decimals=1)
    stats = list(stats.values)
    result0.drop(
        inplace=True,
        labels=[
            "composition", "run_count", "level_mean", "level_std", "level_max"
        ],
        axis=1,
    )
    t0 = time.time()
    # Drop DPS columns that are all 0.
    mask = list(result0.sum(axis=0) != 0)
    result0 = result0.loc[:, mask]

    # rearrange columns
    new_order = ["tank", "healer"]
    dps_cols = [dps for dps in result0.columns if dps not in new_order]
    dps_order = result0[dps_cols].sum(axis=0)
    dps_order = dps_order.sort_values(ascending=False,
                                      inplace=False).index.values
    new_order.extend(dps_order)
    result0 = result0[new_order]

    rows = result0.apply(lambda row: list(row), axis=1)
    colors = dict([[spec["token"], spec["color"]]
                   for spec in blizzcolors.Specs().specs])
    chars = 10
    if len(result0.columns) >= 20:
        chars = 2
    elif len(result0.columns) < 20 and len(result0.columns) >= 17:
        chars = 3
    elif len(result0.columns) < 17 and len(result0.columns) >= 10:
        chars = 4
    abbr = dict([[spec["token"], spec["abbr"][:chars]]
                 for spec in blizzcolors.Specs().specs])
    table = html.Table(children=[])
    for row_index, row in enumerate(rows):
        row_ = html.Tr(children=[
            html.Td(stats[row_index][0]),
            html.Td("%1.1f" % stats[row_index][1]),
            html.Td(stats[row_index][2]),
        ])
        bg_color = "lightgray" if row_index % 2 == 0 else "white"
        for index, cell in enumerate(row):
            style = {}
            if result0.columns[index] in ["tank", "healer"]:
                color = colors[cell]
                style = {
                    "background-color": "rgb(%d,%d,%d)" % color,
                    "border": "1px solid black",
                }
                title = cell.upper().replace("_", " ")
                cell = abbr[cell]
            elif cell == 0:
                style = {"background-color": bg_color, "color": bg_color}
                title = ""
            else:
                style = {
                    "background-color":
                    "rgb(%d,%d,%d)" % colors[result0.columns[index]],
                    "border":
                    "1px solid black",
                }
                title = result0.columns[index].upper().replace("_", " ")
                if cell == 1:
                    cell = abbr[result0.columns[index]]
                else:
                    cell = "x%d" % cell
            row_.children.append(html.Td(cell, title=title, style=style))
        row_.style = {"background-color": bg_color}
        table.children.append(row_)
    table.children.insert(
        0,
        html.Tr(
            [
                html.Th(column[:chars])
                for column in ["N", "AVG", "MAX", "TANK", "HLR"] +
                [abbr[tkn] for tkn in result0.columns[2:]]
            ],
            style={"font-size": "15px"},
        ),
    )
    return table
Exemple #8
0
    def create_figure(self, bounds: List[int]) -> go.Figure:
        """Creates horizontal bar figure for meta index.

        Parameters
        ----------
        bounds : List[int]
            list of ints that defines key level cohorts to calculate the index

        Returns
        -------
        figure : go.Figure
            The bar chart of spec meta indices
        """
        spec_meta = self._calculate_index(bounds)
        spec_meta.sort_values(by="spec_meta_index", inplace=True)
        spec_utils = blizzcolors.Specs()

        fig = go.Figure()
        fig = self._add_tier_annotations(fig)
        trace = go.Bar(
            x=list(spec_meta.spec_meta_index),
            y=list(range(len(spec_meta))),
            marker_color=[
                "rgba(%d,%d,%d,0.9)" % spec_utils.get_color(spec_id)
                if spec_utils.get_role(spec_id) == self.spec_role
                or self.spec_role == "all" else "rgba(1,1,1,0.4)"
                for spec_id in spec_meta.index
            ],
            text=[
                spec_utils.get_spec_name(spec_id).upper()
                for spec_id in spec_meta.index
            ],
            hovertemplate="%{text}, meta ratio = %{x:1.2f}<extra></extra>",
            marker_line_color="black",
            orientation="h",
        )
        fig.add_traces(trace)

        fig.update_layout(
            height=1100,
            xaxis_title=
            "Meta Ratio (spec % at meta level / spec % at population level)",
            showlegend=False,
            title=dict(
                text="Spec Tiers",
                font_size=15,
                x=0.5,
                xanchor="center",
                yanchor="top",
            ),
            yaxis_range=[-1, len(spec_meta.index)],
        )
        # This is annoying... I have to update bar labels separetly.
        # I can't do it as part of go.Bar() definition, because some of the labels
        # are identical and plotly merges them into a single bar
        # (eg Frost mage and Frost DK are shown as a single bar)
        fig.update_yaxes(
            tickmode="array",
            tickvals=trace.y,
            ticktext=[
                spec_utils.get_spec_name(spec_id).upper()
                for spec_id in spec_meta.index
            ],
        )
        return fig
Exemple #9
0
 def __init__(self, data, patch):
     """Inits with spec run data."""
     self.data = data.sum(axis=1).sort_values(ascending=False)
     self.patch = patch
     self.specs = blizzcolors.Specs()