This document captures design and implementation notes for BreakdownChart.
For end-user usage and examples, see BreakdownChart.
Title visual (top)BreakdownChart : VisualSegments : BindableList<BreakdownSegment> (read-only property)
Title : Visual?LegendPlacement : BreakdownLegendPlacement (Above / Below)ShowPercentages : bool (default: true)ShowValues : bool (default: false)SegmentClicked (bubble routing)
BreakdownSegmentClickedEventArgs carries:
Index : intSegment : BreakdownSegmentBreakdownSegment is a state container (not a Visual) with bindables:
Value : doubleLabel : Visual?Color : Color?Tooltip : Visual?Segments are attached/detached when added/removed from Segments so they can participate in UI element ownership:
BreakdownSegment : DispatcherObject, IVisualElementInternally the chart hosts two child visuals (and an optional third):
BreakdownBar (renders the segmented row and handles hover/click)BreakdownLegend (renders the legend, in either compact or expanded layout)Title is attached as a child when non-nullChild order depends on LegendPlacement:
Above: Title?, Legend, BarBelow: Title?, Bar, LegendThe chart measures:
Title with the full provided width and height constraints.Bar with height forced to 1.Legend with the remaining height after Title + bar.The natural size is:
max(TitleWidth, BarWidth, LegendWidth)TitleHeight + 1 + LegendHeightSize hints:
growX = 1 (the chart likes to stretch horizontally)growY = 0 (height is content-driven)Arranges:
The bar draws a single line of segment cells.
From BreakdownStyle:
FillRune (default: space) — the rune stamped in segment cellsSegmentGap (default: 1) — empty cells between segmentsBarStyle — base style used for the bar (defaults to theme.ControlFillStyle())From each BreakdownSegment:
Value (negative values are treated as 0 for sizing)Color (optional override)Given:
W = Bounds.Widthgap = max(0, SegmentGap)n = Segments.CountUsable bar width is:
usable = max(0, W - gap * max(0, n - 1))If n == 0, usable == 0, or total value is <= 0, the bar is filled with FillRune using the base style.
Otherwise:
total = sum(max(0, segment.Value))floor((value / total) * usable)Value > 0, add 1 width until remaining is 0This guarantees:
usableSegment cell background is chosen in order:
segment.ColorBreakdownStyle.DefaultSegmentColors (cycled)The bar fills:
FillRune and the chosen background colorBounds.Width) with FillRune and base styleLegend layout is configured by BreakdownStyle:
LegendLayout : BreakdownLegendLayout
Compact: uses WrapHStackExpanded: uses VStack (one item per row)LegendItemSpacing : int (compact spacing, default 4)LegendJustify : WrapJustify (compact justification, default SpaceBetween)LegendStyle : Style? (applied to legend text)LegendMutedStyle : Style? (applied to suffix text: percentages/values)Each segment produces a LegendItem visual composed of:
■) in the segment colorLabel (via a ComputedVisual so changes are tracked)TextBlock containing:
(NN%) when ShowPercentages == trueShowValues == trueFormatting uses Visual.ToStringValue(...), which is culture-aware (see CultureStyle).
Legend items are reused:
_items list grows/shrinks with Segments.CountLegendLayout moves the same legend item instances between the wrap layout and the expanded layoutThis is important because BreakdownSegment.Label is a Visual instance: re-creating legend items while reusing the
same label instances would trigger “visual already has a parent” failures.
Hover is handled by the bar (BreakdownBar):
Tooltip content selection:
BreakdownSegment.Tooltip if providedLabel (if non-null)Value (Percent%)The default tooltip currently reuses BreakdownSegment.Label (a Visual) inside the tooltip tree.
A visual cannot have two parents, so if you need the legend label and the tooltip label simultaneously, provide
an explicit BreakdownSegment.Tooltip instead of relying on the default tooltip.
Tooltips are shown via TooltipWindow:
AnchorRect = 1x1)While a tooltip is visible, the bar registers as an IAnimatedVisual and requests animation ticks so it can close the
tooltip when hover is lost (even if no further mouse events arrive).
Click selection is press/release-based:
SegmentClickedBreakdownStyle is resolved from the environment via BreakdownStyle.Key.
Defaults:
FillRune = ' ' (segments are primarily background-color blocks)SegmentGap = 1LegendLayout = CompactLegendItemSpacing = 4LegendJustify = SpaceBetweenTests that lock down current behavior:
src/XenoAtom.Terminal.UI.Tests/BreakdownTests.cs
SegmentClicked routed eventBreakdownSegment.Label for the default tooltip (clone or render as text) to prevent parent conflicts.