Skip to content

Bar Chart

BarChart totals a field of its events into one bar per category.

import dachshund
from dachshund.widgets import BarChart

with dachshund.row():
    dachshund.add(BarChart("commands", x="name"))

dachshund.emit("commands", name="/ban", value=1)

dachshund.widgets.chart.bar.BarChart dataclass

BarChart(name, *, source='', title=None, actions=list(), action_args=dict(), persist=False, x, y='value', aggregate='sum')

Bases: KeyedWidget

A bar per category, aggregating a field of its events: its total by default.

Bars keep the order their categories first appeared in.

Example

with dachshund.row():
    dachshund.add(BarChart("commands", x="name"))
dachshund.emit("commands", name="/ban", value=1)

Options

  • x (str) - The field naming an event's category. Required.
  • y (str) - The field aggregated per category. Defaults to "value".
  • aggregate (str) - "sum", "count", "average", "min", "max" or "distinct" of y per category. "count" counts events, whatever y. Defaults to "sum". See Aggregating.
  • persist (bool) - Keeps the bars across restarts. Needs the dashboard's storage. Defaults to False.

Removing a category

A truthy _delete field removes the event's category:

dachshund.emit("commands", name="/ban", _delete=True)

On a persisted chart, sweep(before) removes every category not seen since before, see KeyedWidget.

x instance-attribute

x

y class-attribute instance-attribute

y = 'value'

aggregate class-attribute instance-attribute

aggregate = 'sum'

type class-attribute

type = 'bar'

op class-attribute

op = 'replace'

script class-attribute

script = Path(__file__).parent / 'charts.js'

style class-attribute

style = Path(__file__).parent / 'charts.css'

series property

series

The fields this chart draws, a set of bars each.

render

render(events)

Aggregate every event into the bar of its category.

A persisting chart reads its own kept bars instead, since those cover more than whatever events are still around to re-aggregate.

Source code in dachshund/widgets/chart/bar.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def render(self, events: Sequence[Event]) -> dict[str, Any]:
    """Aggregate every event into the bar of its category.

    A persisting chart reads its own kept bars instead, since those cover
    more than whatever events are still around to re-aggregate.
    """
    if self.persist:
        entries = self.state.entries
    else:
        entries = {}

        for event in events:
            category = self.category(event)
            entries[category] = self.tally(entries.get(category), event)

    bars = {}

    for category, entry in entries.items():
        value = aggregated(_bucket(entry), self.aggregate)

        if value is not None:
            bars[category] = value

    return {
        "series": list(self.series),
        "x": list(bars),
        "y": [list(bars.values())],
    }

category

category(event)

Which bar the event belongs to.

Source code in dachshund/widgets/chart/bar.py
 98
 99
100
def category(self, event: Event) -> str:
    """Which bar the event belongs to."""
    return str(event.payload.get(self.x, UNLABELLED))

tally

tally(entry, event)

A category's entry with event added: how many events, and y's numbers.

Source code in dachshund/widgets/chart/bar.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def tally(self, entry: Any, event: Event) -> dict[str, Any]:
    """A category's entry with `event` added: how many events, and `y`'s numbers."""
    kept = _entry(entry)
    value = self.value(event, self.y) if self.aggregate != "count" else None

    kept["count"] += 1

    if value is not None:
        kept["values"] += 1
        kept["sum"] += value
        kept["min"] = value if kept["min"] is None else min(kept["min"], value)
        kept["max"] = value if kept["max"] is None else max(kept["max"], value)

    if self.aggregate == "distinct" and kept["distinct"] is not None:
        raw = event.payload.get(self.y)

        if raw is not None:
            labels = set(kept["distinct"]) | {_label(raw)}
            kept["distinct"] = (
                sorted(labels) if len(labels) <= DISTINCT_CAP else None
            )

    return kept

accumulate

accumulate(event)

Fold one more event into the kept bars.

A truthy _delete removes the category instead of adding to it.

Source code in dachshund/widgets/chart/bar.py
126
127
128
129
130
131
132
133
134
135
136
137
138
def accumulate(self, event: Event) -> None:
    """Fold one more event into the kept bars.

    A truthy `_delete` removes the category instead of adding to it.
    """
    category = self.category(event)

    if event.payload.get("_delete"):
        self.state.remove(category)
        return

    entry = self.tally(self.state.entries.get(category), event)
    self.state.upsert(category, entry, event.at.timestamp())