Skip to content

anydyce package reference

HPlotterChooser

Experimental

This class should be considered experimental and may change or disappear in future versions.

A controller for coordinating the display of a histogram data set and selection of one or more plotters as well as triggering updates in response to either control or data changes. All parameters for the initializer are optional.

histogram_specs is the histogram data set which defaults to an empty tuple. If provided, each item therein can be a dyce.H object, a 2-tuple, or a 3-tuple. 2-tuples are in the format (str, H), where str is a name or description that will be used to identify the accompanying H object where it appears in the visualization. 3-tuples are in the format (str, H, H). The second H object is used for the interior ring in “burst” break-out graphs, but otherwise ignored. If an item is None, it is roughly synonymous with ("", H({}), None), with the exception that it does not advance the automatic naming counter. This can be useful as “blank” filler to achieve a desired layout (e.g., where one wants to compare across burst graphs that don't neatly fit into a particular row size).

The histogram data set can also be replaced via update_hs.

Plotter controls (including the selection tabs) are contained within an accordion interface. If controls_expanded is True, the accordion is initially expanded for the user. If it is False, it is initially collapsed.

plot_widgets allows object creators to customize the available control widgets, including their initial values. It defaults to None which results in a fresh PlotWidgets object being created during construction.

plotters_or_factories allows overriding which plotters are available. The default is to provide factories for all plotters currently available in anydyce.

selected_name is the name of the plotter to be displayed initially. It must match the HPlotter.NAME property of an available plotter provided by the plotters_or_factories parameter.

Source code in anydyce/viz.py
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
class HPlotterChooser:
    r"""
    !!! warning "Experimental"

        This class should be considered experimental and may change or disappear in
        future versions.

    A controller for coordinating the display of a histogram data set and selection of
    one or more plotters as well as triggering updates in response to either control or
    data changes. All parameters for the
    [initializer][anydyce.viz.HPlotterChooser.__init__] are optional.

    *histogram_specs* is the histogram data set which defaults to an empty tuple. If
    provided, each item therein can be a ``#!python dyce.H`` object, a 2-tuple, or a
    3-tuple. 2-tuples are in the format ``#!python (str, H)``, where ``#!python str`` is
    a name or description that will be used to identify the accompanying ``#!python H``
    object where it appears in the visualization. 3-tuples are in the format ``#!python
    (str, H, H)``. The second ``#!python H`` object is used for the interior ring in
    “burst” break-out graphs, but otherwise ignored. If an item is ``#!python None``, it
    is roughly synonymous with ``#!python ("", H({}), None)``, with the exception that
    it does not advance the automatic naming counter. This can be useful as “blank”
    filler to achieve a desired layout (e.g., where one wants to compare across burst
    graphs that don't neatly fit into a particular row size).

    The histogram data set can also be replaced via
    [``update_hs``][anydyce.viz.HPlotterChooser.update_hs].

    Plotter controls (including the selection tabs) are contained within an accordion
    interface. If *controls_expanded* is ``#!python True``, the accordion is initially
    expanded for the user. If it is ``#!python False``, it is initially collapsed.

    *plot_widgets* allows object creators to customize the available control widgets,
    including their initial values. It defaults to ``#!python None`` which results in a
    fresh [``PlotWidgets``][anydyce.viz.PlotWidgets] object being created during
    construction.

    *plotters_or_factories* allows overriding which plotters are available. The default
    is to provide factories for all plotters currently available in ``anydyce``.

    *selected_name* is the name of the plotter to be displayed initially. It must match
    the [``HPlotter.NAME`` property][anydyce.viz.HPlotter.NAME] of an available plotter
    provided by the *plotters_or_factories* parameter.
    """

    @beartype
    def __init__(
        self,
        histogram_specs: Iterable[
            Optional[
                Union[HLikeT, Tuple[str, HLikeT], Tuple[str, HLikeT, Optional[HLikeT]]]
            ]
        ] = (),
        *,
        controls_expanded: bool = False,
        plot_widgets: Optional[PlotWidgets] = None,
        plotters_or_factories: Iterable[Union[HPlotter, HPlotterFactoryT]] = (
            BurstHPlotter,
            LineHPlotter,
            BarHPlotter,
            ScatterHPlotter,
            HorizontalBarHPlotter,
        ),
        selected_name: Optional[str] = None,
    ):
        plotters = tuple(
            plotter if isinstance(plotter, HPlotter) else plotter()
            for plotter in plotters_or_factories
        )

        if not plotters:
            raise ValueError("must provide at least one plotter")

        self._plotters_by_name: Mapping[str, HPlotter] = {
            plotter.NAME: plotter for plotter in plotters
        }

        assert self._plotters_by_name

        if selected_name is None:
            selected_name = next(iter(self._plotters_by_name))

        if selected_name is not None and selected_name not in self._plotters_by_name:
            raise ValueError(
                f"selected_name {selected_name!r} does not match any plotter"
            )

        if len(self._plotters_by_name) < len(plotters):
            duplicate_names = ", ".join(
                repr(plotter_name)
                for plotter_name, count in Counter(
                    plotter.NAME for plotter in plotters
                ).items()
                if count > 1
            )
            warnings.warn(
                f"ignoring redundant plotters with duplicate names {duplicate_names}",
                category=RuntimeWarning,
            )

        if plot_widgets is None:
            plot_widgets = PlotWidgets()

        self._plot_widgets = plot_widgets
        self._layouts_by_name: Mapping[str, widgets.Widget] = {}

        for plotter_name, plotter in self._plotters_by_name.items():
            self._layouts_by_name[plotter_name] = plotter.layout(plot_widgets)

        self._hs: Tuple[Tuple[str, H, Optional[H]], ...] = ()
        self._hs_culled: Tuple[Tuple[str, H, Optional[H]], ...] = ()
        self._cutoff: Optional[float] = None
        self._csv_download_link = ""
        self.update_hs(histogram_specs)
        self._selected_plotter: Optional[HPlotter]
        tab_names = tuple(self._plotters_by_name.keys())

        chooser_tab = widgets.Tab(
            children=tuple(self._layouts_by_name.values()),
            selected_index=0
            if selected_name is None
            else tab_names.index(selected_name),
            titles=tab_names,
        )

        def _handle_tab(change) -> None:
            assert change["name"] == "selected_index"
            self._selected_plotter = next(
                islice(self._plotters_by_name.values(), change["new"], None)
            )
            self._trigger_update()

        chooser_tab.observe(_handle_tab, names="selected_index")

        self._selected_plotter = next(
            islice(self._plotters_by_name.values(), chooser_tab.selected_index, None)
        )

        self._out = widgets.VBox(
            [
                widgets.Accordion(
                    children=[chooser_tab],
                    titles=["Plot Controls"],
                    selected_index=0 if controls_expanded else None,
                ),
                widgets.interactive_output(self.plot, self._plot_widgets.asdict()),
            ]
        )

    @beartype
    def interact(self) -> None:
        r"""
        Displays the container responsible for selecting which plotter is used.
        """
        display(self._out)

    @beartype
    # @debounce
    def plot(self, **kw) -> None:
        r"""
        Callback for updating the visualization in response to configuration or data
        changes. *settings* are the current values from all control widgets. (See
        [``PlotWidgets``][anydyce.viz.PlotWidgets].)
        """
        settings = cast(SettingsDict, kw)
        cutoff = (
            self._plot_widgets.cutoff.value
            if self._plot_widgets.enable_cutoff.value
            else None
        )

        if self._cutoff != cutoff:
            self._cutoff = cutoff
            self._cull_data()

        with matplotlib.style.context(settings["plot_style"]):
            if self._selected_plotter is not None:
                self._selected_plotter.plot(self._hs_culled, settings)
                transparent = self._selected_plotter.transparent(
                    settings["burst_color_bg_trnsp"]
                )
            else:
                transparent = False

            buf = io.BytesIO()
            matplotlib.pyplot.savefig(
                buf,
                bbox_inches="tight",
                format=settings["img_type"],
                transparent=transparent,
            )
            img_name = "-".join(label for label, _, _ in self._hs)
            img = Image(img_name, settings["img_type"], buf.getvalue())
            display(HTML(f"{self._csv_download_link} | {img.download_link()}"))
            display(img)
            matplotlib.pyplot.clf()
            matplotlib.pyplot.close()

    @beartype
    def update_hs(
        self,
        histogram_specs: Iterable[
            Optional[
                Union[HLikeT, Tuple[str, HLikeT], Tuple[str, HLikeT, Optional[HLikeT]]]
            ]
        ],
    ) -> None:
        r"""
        Triggers an update to the histogram data. See
        [``HPlotterChooser``][anydyce.viz.HPlotterChooser] for a more detailed
        explanation of *histogram_specs*.
        """
        self._hs = _histogram_specs_to_h_tuples(histogram_specs, cutoff=None)
        self._csv_download_link = _csv_download_link(self._hs)

        self._plot_widgets.burst_swap.disabled = all(
            h_outer is None or h_inner == h_outer for _, h_inner, h_outer in self._hs
        )

        self._cull_data()
        self._trigger_update()

    @beartype
    def _cull_data(self) -> None:
        self._hs_culled = _histogram_specs_to_h_tuples(self._hs, self._cutoff)

    @beartype
    def _trigger_update(self) -> None:
        self._plot_widgets._rev_no.value += 1

__init__(histogram_specs: Iterable[Optional[Union[HLikeT, Tuple[str, HLikeT], Tuple[str, HLikeT, Optional[HLikeT]]]]] = (), *, controls_expanded: bool = False, plot_widgets: Optional[PlotWidgets] = None, plotters_or_factories: Iterable[Union[HPlotter, HPlotterFactoryT]] = (BurstHPlotter, LineHPlotter, BarHPlotter, ScatterHPlotter, HorizontalBarHPlotter), selected_name: Optional[str] = None)

Source code in anydyce/viz.py
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
@beartype
def __init__(
    self,
    histogram_specs: Iterable[
        Optional[
            Union[HLikeT, Tuple[str, HLikeT], Tuple[str, HLikeT, Optional[HLikeT]]]
        ]
    ] = (),
    *,
    controls_expanded: bool = False,
    plot_widgets: Optional[PlotWidgets] = None,
    plotters_or_factories: Iterable[Union[HPlotter, HPlotterFactoryT]] = (
        BurstHPlotter,
        LineHPlotter,
        BarHPlotter,
        ScatterHPlotter,
        HorizontalBarHPlotter,
    ),
    selected_name: Optional[str] = None,
):
    plotters = tuple(
        plotter if isinstance(plotter, HPlotter) else plotter()
        for plotter in plotters_or_factories
    )

    if not plotters:
        raise ValueError("must provide at least one plotter")

    self._plotters_by_name: Mapping[str, HPlotter] = {
        plotter.NAME: plotter for plotter in plotters
    }

    assert self._plotters_by_name

    if selected_name is None:
        selected_name = next(iter(self._plotters_by_name))

    if selected_name is not None and selected_name not in self._plotters_by_name:
        raise ValueError(
            f"selected_name {selected_name!r} does not match any plotter"
        )

    if len(self._plotters_by_name) < len(plotters):
        duplicate_names = ", ".join(
            repr(plotter_name)
            for plotter_name, count in Counter(
                plotter.NAME for plotter in plotters
            ).items()
            if count > 1
        )
        warnings.warn(
            f"ignoring redundant plotters with duplicate names {duplicate_names}",
            category=RuntimeWarning,
        )

    if plot_widgets is None:
        plot_widgets = PlotWidgets()

    self._plot_widgets = plot_widgets
    self._layouts_by_name: Mapping[str, widgets.Widget] = {}

    for plotter_name, plotter in self._plotters_by_name.items():
        self._layouts_by_name[plotter_name] = plotter.layout(plot_widgets)

    self._hs: Tuple[Tuple[str, H, Optional[H]], ...] = ()
    self._hs_culled: Tuple[Tuple[str, H, Optional[H]], ...] = ()
    self._cutoff: Optional[float] = None
    self._csv_download_link = ""
    self.update_hs(histogram_specs)
    self._selected_plotter: Optional[HPlotter]
    tab_names = tuple(self._plotters_by_name.keys())

    chooser_tab = widgets.Tab(
        children=tuple(self._layouts_by_name.values()),
        selected_index=0
        if selected_name is None
        else tab_names.index(selected_name),
        titles=tab_names,
    )

    def _handle_tab(change) -> None:
        assert change["name"] == "selected_index"
        self._selected_plotter = next(
            islice(self._plotters_by_name.values(), change["new"], None)
        )
        self._trigger_update()

    chooser_tab.observe(_handle_tab, names="selected_index")

    self._selected_plotter = next(
        islice(self._plotters_by_name.values(), chooser_tab.selected_index, None)
    )

    self._out = widgets.VBox(
        [
            widgets.Accordion(
                children=[chooser_tab],
                titles=["Plot Controls"],
                selected_index=0 if controls_expanded else None,
            ),
            widgets.interactive_output(self.plot, self._plot_widgets.asdict()),
        ]
    )

interact() -> None

Displays the container responsible for selecting which plotter is used.

Source code in anydyce/viz.py
1224
1225
1226
1227
1228
1229
@beartype
def interact(self) -> None:
    r"""
    Displays the container responsible for selecting which plotter is used.
    """
    display(self._out)

plot(**kw) -> None

Callback for updating the visualization in response to configuration or data changes. settings are the current values from all control widgets. (See PlotWidgets.)

Source code in anydyce/viz.py
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
@beartype
# @debounce
def plot(self, **kw) -> None:
    r"""
    Callback for updating the visualization in response to configuration or data
    changes. *settings* are the current values from all control widgets. (See
    [``PlotWidgets``][anydyce.viz.PlotWidgets].)
    """
    settings = cast(SettingsDict, kw)
    cutoff = (
        self._plot_widgets.cutoff.value
        if self._plot_widgets.enable_cutoff.value
        else None
    )

    if self._cutoff != cutoff:
        self._cutoff = cutoff
        self._cull_data()

    with matplotlib.style.context(settings["plot_style"]):
        if self._selected_plotter is not None:
            self._selected_plotter.plot(self._hs_culled, settings)
            transparent = self._selected_plotter.transparent(
                settings["burst_color_bg_trnsp"]
            )
        else:
            transparent = False

        buf = io.BytesIO()
        matplotlib.pyplot.savefig(
            buf,
            bbox_inches="tight",
            format=settings["img_type"],
            transparent=transparent,
        )
        img_name = "-".join(label for label, _, _ in self._hs)
        img = Image(img_name, settings["img_type"], buf.getvalue())
        display(HTML(f"{self._csv_download_link} | {img.download_link()}"))
        display(img)
        matplotlib.pyplot.clf()
        matplotlib.pyplot.close()

update_hs(histogram_specs: Iterable[Optional[Union[HLikeT, Tuple[str, HLikeT], Tuple[str, HLikeT, Optional[HLikeT]]]]]) -> None

Triggers an update to the histogram data. See HPlotterChooser for a more detailed explanation of histogram_specs.

Source code in anydyce/viz.py
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
@beartype
def update_hs(
    self,
    histogram_specs: Iterable[
        Optional[
            Union[HLikeT, Tuple[str, HLikeT], Tuple[str, HLikeT, Optional[HLikeT]]]
        ]
    ],
) -> None:
    r"""
    Triggers an update to the histogram data. See
    [``HPlotterChooser``][anydyce.viz.HPlotterChooser] for a more detailed
    explanation of *histogram_specs*.
    """
    self._hs = _histogram_specs_to_h_tuples(histogram_specs, cutoff=None)
    self._csv_download_link = _csv_download_link(self._hs)

    self._plot_widgets.burst_swap.disabled = all(
        h_outer is None or h_inner == h_outer for _, h_inner, h_outer in self._hs
    )

    self._cull_data()
    self._trigger_update()

jupyter_visualize(histogram_specs: Iterable[Optional[Union[HLikeT, Tuple[str, HLikeT], Tuple[str, HLikeT, Optional[HLikeT]]]]], *, controls_expanded: bool = False, initial_alpha: float = DEFAULT_ALPHA, initial_burst_cmap_inner: str = DEFAULT_CMAP_BURST_INNER, initial_burst_cmap_link: bool = True, initial_burst_cmap_outer: str = DEFAULT_CMAP_BURST_OUTER, initial_burst_color_bg: str = DEFAULT_COLOR_BG, initial_burst_color_bg_trnsp: bool = False, initial_burst_color_text: str = DEFAULT_COLOR_TEXT, initial_burst_columns: int = DEFAULT_COLS_BURST, initial_burst_swap: bool = False, initial_burst_zero_fill_normalize: bool = False, initial_enable_cutoff: bool = True, initial_graph_type: TraditionalPlotType = TraditionalPlotType.NORMAL, initial_img_type: ImageType = ImageType.PNG, initial_markers: str = DEFAULT_MARKERS, initial_plot_style: str = DEFAULT_PLOT_STYLE, initial_resolution: int = DEFAULT_RESOLUTION, initial_show_shadow: bool = False, selected_name: Optional[str] = None)

Experimental

This function should be considered experimental and may change or disappear in future versions.

Takes a list of one or more histogram_specs and produces an interactive visualization reminiscent of AnyDice, but with some extra goodies.

The “Powered by the Apocalypse (PbtA)” example in the introduction notebook should give an idea of the effect. (See Interactive quick start.)

Parameters have the same meanings as with HPlotterChooser and PlotWidgets.

Source code in anydyce/viz.py
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
@experimental
@beartype
def jupyter_visualize(
    histogram_specs: Iterable[
        Optional[
            Union[HLikeT, Tuple[str, HLikeT], Tuple[str, HLikeT, Optional[HLikeT]]]
        ]
    ],
    *,
    controls_expanded: bool = False,
    initial_alpha: float = DEFAULT_ALPHA,
    initial_burst_cmap_inner: str = DEFAULT_CMAP_BURST_INNER,
    initial_burst_cmap_link: bool = True,
    initial_burst_cmap_outer: str = DEFAULT_CMAP_BURST_OUTER,
    initial_burst_color_bg: str = DEFAULT_COLOR_BG,
    initial_burst_color_bg_trnsp: bool = False,
    initial_burst_color_text: str = DEFAULT_COLOR_TEXT,
    initial_burst_columns: int = DEFAULT_COLS_BURST,
    initial_burst_swap: bool = False,
    initial_burst_zero_fill_normalize: bool = False,
    initial_enable_cutoff: bool = True,
    initial_graph_type: TraditionalPlotType = TraditionalPlotType.NORMAL,
    initial_img_type: ImageType = ImageType.PNG,
    initial_markers: str = DEFAULT_MARKERS,
    initial_plot_style: str = DEFAULT_PLOT_STYLE,
    initial_resolution: int = DEFAULT_RESOLUTION,
    initial_show_shadow: bool = False,
    selected_name: Optional[str] = None,
):
    r"""
    !!! warning "Experimental"

        This function should be considered experimental and may change or disappear in
        future versions.

    Takes a list of one or more *histogram_specs* and produces an interactive
    visualization reminiscent of [AnyDice](https://anydice.com/), but with some extra
    goodies.

    The “Powered by the _Apocalypse_ (PbtA)” example in the introduction notebook should
    give an idea of the effect. (See [Interactive quick
    start](index.md#interactive-quick-start).)

    Parameters have the same meanings as with
    [``HPlotterChooser``][anydyce.viz.HPlotterChooser] and
    [``PlotWidgets``][anydyce.viz.PlotWidgets].
    """
    plotter_chooser = HPlotterChooser(
        histogram_specs,
        controls_expanded=controls_expanded,
        plot_widgets=PlotWidgets(
            initial_alpha=initial_alpha,
            initial_burst_cmap_inner=initial_burst_cmap_inner,
            initial_burst_cmap_link=initial_burst_cmap_link,
            initial_burst_cmap_outer=initial_burst_cmap_outer,
            initial_burst_color_bg=initial_burst_color_bg,
            initial_burst_color_bg_trnsp=initial_burst_color_bg_trnsp,
            initial_burst_color_text=initial_burst_color_text,
            initial_burst_columns=initial_burst_columns,
            initial_burst_swap=initial_burst_swap,
            initial_burst_zero_fill_normalize=initial_burst_zero_fill_normalize,
            initial_enable_cutoff=initial_enable_cutoff,
            initial_graph_type=initial_graph_type,
            initial_img_type=initial_img_type,
            initial_markers=initial_markers,
            initial_plot_style=initial_plot_style,
            initial_resolution=initial_resolution,
            initial_show_shadow=initial_show_shadow,
        ),
        selected_name=selected_name,
    )

    plotter_chooser.interact()