Skip to content

BarChartWidget: cap bar count with a MaxBars top-N limit (+ aggregated "Other" bar) #269

Description

@HanSur94

Problem / motivation

BarChartWidget plots every value in the resolved data vector as its own bar, with no way to cap the number shown. Verified in libs/Dashboard/BarChartWidget.m:60–86: refresh resolves data/cats then hands the whole vector to bar/barh. The only public knobs are DataFcn, Orientation, and Stacked (:2–6).

For a high-cardinality categorical view — per-sensor peak values, fault counts by channel, "biggest contributors" — this renders dozens of unreadable hair-thin bars, and there is no way to focus on the worst offenders. The canonical "top-N" / focused-Pareto-head analysis is impossible today.

This is distinct from #268 (Sort, which only reorders the bars already shown) — Sort changes order, MaxBars changes cardinality — and from the annotation items #234 (value labels), #253 (axis labels), #211 (threshold overlays). None of those reduces the bar count.

Proposed feature

Add an opt-in MaxBars limit. When the data has more categories than MaxBars, keep the MaxBars-1 largest values and fold the remainder into a single aggregate bar labelled OtherLabel (default 'Other'). Selection is by magnitude, independent of #268's display Sort.

b = BarChartWidget('DataFcn', @perSensorPeaks, ...
                   'MaxBars', 8, ...        % keep 7 tallest + one "Other"
                   'OtherLabel', 'Other');  % '' => drop the remainder instead

Rough sketch

  • Lib/class: libs/Dashboard/BarChartWidget.m (single file).
  • Public API: MaxBars = Inf (default → byte-identical to today, no limit) and OtherLabel = 'Other'.
  • Render: after data/cats are resolved (:60) and before the bar plot, a ~6-line helper: get top-N indices via sort(data,'descend'), rebuild data as [data(idx(1:k)); sum(data(idx(k+1:end)))] and cats likewise with the Other label (k = MaxBars-1). When the limit is active, take the cla+replot path rather than the in-place YData fast-path (:62–67) so the reduced set and its tick labels stay in sync each tick. Works for both bar/barh (orientation branch untouched). No-op when data already ≤ MaxBars. If OtherLabel = '', drop the remainder instead of aggregating.
  • Serialize: emit s.maxBars / s.otherLabel in toStruct (:124–132) only when non-default; read with isfield guards in fromStruct (:136–158) defaulting to Inf / 'Other' — same round-trip pattern as orientation/stacked (:126–127, :144–145). Old saves load unbounded and render unchanged.

Value

Medium-High. "Top-N worst offenders / biggest contributors" is a standard exploratory move for a sensor-analysis engineer facing many channels or categories. Today a 40-channel DataFcn produces 40 hair-thin bars and the eye cannot pick the worst few; MaxBars turns the tile into a focused ranking. Natural pair with #268 (Sort) — sorted + capped = the head of a Pareto — but independently useful.

Constraints check

  • Toolbox-free: yes — only base sort / sum.
  • Backward-compatible: yes — default MaxBars = Inf is byte-identical to current behavior; old serialized dashboards load unbounded via isfield guards.
  • Pure MATLAB/Octave: yes.
  • Widget contract: unchanged — additive props + existing toStruct/fromStruct/refresh, no base-class or layout change.

Effort estimate

S — single file: two public props + a ~6-line top-N/Other reduction helper + a fast-path guard + a toStruct/fromStruct round-trip. Plus one test: MaxBars=3 on [5 1 4 2 3] (cats {a..e}) → heights [5 4 6] labels {a,c,Other} (Other = 1+2+3); MaxBars=Inf preserves input unchanged; values round-trip through serialization.


AI-proposed via /feature-scout — needs a human product decision before implementation.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions