Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

UsageChart

A UsageChart draws one horizontal bar divided into segments that share a total. The width of a segment is its share of that total. Values are u64, so a segment cannot be negative. A segment with the value 0, or one that does not fit, stays in the chart (and keeps its index) but takes no space. The largest values are given space first. A segment that would otherwise be less than one cell wide gets one cell when any width remains.

Create one with UsageChart::new (a layout and initialization flags) or with the usagechart! macro:

let chart = UsageChart::new(layout!("d:f"), usagechart::Flags::None);
let c1 = usagechart!("d:f");
let c2 = usagechart!("x:1,y:1,w:40,h:3,segments:[10,20,30]");
let c3 = usagechart!("d:f,flags:Margins+DimSegmentsOnSelection,total:256,lc:LabelAndPercentage,segments:[{48,'OS'},{36,'Apps',fill:CrossHatch}]");

A new chart starts empty. The segments stay in the order they were added, the total is the sum of the values (so the segments fill the bar), and the hover tooltip shows the label and the value. Values are written with thousands separators (1,234,567). Each segment uses a theme chart color unless it has its own attribute. With Margins the minimum size is 3×3 and the bar is inset by one cell on every side. Without it the minimum size is 3×1 and the bar fills the control.

A UsageChart supports all common parameters (as they are described in Instantiate via Macros section). Besides them, the following named parameters are also accepted:

Parameter nameTypePositional parameterPurpose
flagsFlagsNoInitialization flags
segments or values or dataListNoSegments to add. Each item is a number or a description
sort or sort-type or sorttype or orderStringNoLeft-to-right order of the segments
total or total-usage or totalusageStringNoWhat the full width of the bar represents
content or label-content or lcStringNoWhat the hover tooltip shows
numeric-format or nfDictNoFormat used for the values in the tooltip
selected or select or selected-segmentIntegerNoThe index of the segment that is selected when the chart starts

Flags

  • usagechart::Flags::Margins or Margins (for macro initialization) — insets the bar by one cell on every side. The minimum size becomes 3×3.
  • usagechart::Flags::DimSegmentsOnSelection or DimSegmentsOnSelection — while a segment is selected, every other segment is drawn with the inactive chart color.
let chart = usagechart!("d:f,flags:Margins+DimSegmentsOnSelection,segments:[10,20,30]");

Segments

A segment has a value, a label (that can be empty), a fill character and an optional character attribute. Without an attribute, the segment uses the next theme chart color. A number passed to add_segment or written in the macro list is a solid segment with that value and an empty label.

chart.add_segment(48u64);
chart.add_segment(usagechart::SegmentBuilder::new().value(36).label("Apps").fill(usagechart::SegmentFillType::CrossHatch).build());
chart.add_segments([10u8, 20u16, 30u32]);

In the segments (or values, or data) list, an item is either a number or a dictionary. value is required. The other fields are optional.

Parameter nameTypePositional parameterPurpose
value or val or vIntegerYes (first)The segment value (a positive integer)
label or text or caption or nameStringYes (second)The segment label
fill or fill-type or filltype or ftStringNoThe character that fills the segment
attr or attribute or charattr or colorDictNoCharacter attribute (same syntax as charattr!)
let chart = usagechart!("d:f,segments:[10,{20,'OS'},{30,label:'Apps',fill:Grid,attr:red},{40,Docs,color:{fore:green}}]");

clear removes every segment, the selection and the tooltip. Flags, sort, total, number format and tooltip content stay as they are. The next segment added is the first one again.

Fill

fill chooses the character repeated across a segment. The values are available from code as usagechart::SegmentFillType. Solid is the default.

FillMacro formCharacter
SolidSolid or solid█
Shade75Shade75▓
Shade50Shade50▒
Shade25Shade25░
BrailleBraille⣿
CheckerboardCheckerboard or checkerboard▚
GridGrid or grid┼
GridDoubleGridDouble or grid-double╬
CrossHatchCrossHatch or cross-hatch╳
DashedDashed┇
DiagonalUpDiagonalUp or diagonal-up╱
DiagonalDownDiagonalDown or diagonal-down╲
NotchedNotched■
Custom(char)Custom('#'), Custom(code: 0x2588), or '#'that character

Order

sort (or set_sort_type) sets the left-to-right order. The values are available from code as usagechart::SortType. Lengths are still given to the largest values first, so a segment that does not fit is omitted whatever this order says. A segment’s selection index is its position after this sort.

OrderMacro formDescription
NoneNone or noneThe order in which the segments were added. This is the default
HighestToLowestHighestToLowest or highest-to-lowestLargest value first
LowestToHighestLowestToHighest or lowest-to-highestSmallest value first
AlphabeticalAlphabetical or alphaUsual string order. An empty label comes first
ReverseAlphabeticalReverseAlphabetical or reverse-alphabeticalThe reverse of Alphabetical

Total

total (or set_total_usage) sets what the full width of the bar represents. The values are available from code as usagechart::TotalUsage.

ValueMacro formDescription
AutoAuto or autoThe total is the sum of the segment values, so they fill the bar. This is the default
Fixed(n)Fixed(n) or fix(n) or the number nThe total is n. The unused part of the bar stays empty. A total of 0 draws no segments
let drive = usagechart!("d:f,total:256,segments:[{48,'OS'},{36,'Apps'}]");
chart.set_total_usage(usagechart::TotalUsage::Fixed(256));

Tooltip

content (or set_label_content) chooses what the hover tooltip shows. The values are available from code as usagechart::LabelContent. Read the current choice with label_content.

ValueMacro formShows
LabelAndValueLabelAndValue or label-and-value or label+valuelabel: value. This is the default
ValueValueThe value of the segment
LabelLabelThe label of the segment
PercentagePercentage or percentThe share of the total (18.75%)
LabelAndPercentageLabelAndPercentage or label-and-percentage or label+percentagelabel: percentage

With LabelAndValue and LabelAndPercentage, a segment with an empty label shows only its value or percentage. With Label, a segment with an empty label shows no tooltip. A percentage always has two decimals and a % suffix. It does not use the number format.

Number format

numeric-format (or set_number_format) controls how values are written in the tooltip. The macro uses the same syntax as numericformat!. The default groups digits by thousands.

let chart = usagechart!("d:f,lc:LabelAndValue,nf:{dec,group:3,suffix:' GB'}");
chart.set_number_format(FormatNumber::new(10).group(3, b',').suffix(" GB"));

Selection

A segment is selected by clicking it, with the keyboard, or with set_selected_segment. The selected segment is outlined with the theme selection border and, with DimSegmentsOnSelection, the other segments are dimmed. The outline stays when the chart loses the focus. Segments that take no space are skipped. A disabled chart is drawn with the inactive color and shows no selection.

The selection is cleared when the user clicks the empty part of the bar or presses Escape. clear also clears it. Losing the focus does not.

index is the segment’s position after sorting. With SortType::None that is the order in which the segments were added.

Events

To intercept events from a UsageChart, implement UsageChartEvents on the window that runs the event loop. The attribute events = UsageChartEvents generates the trait. Both methods have a default implementation that returns EventProcessStatus::Ignored.

pub trait UsageChartEvents {
    fn on_usage_segment_selected(&mut self, handle: Handle<UsageChart>, index: usize) -> EventProcessStatus {...}
    fn on_clear_selection(&mut self, handle: Handle<UsageChart>) -> EventProcessStatus {...}
}

on_usage_segment_selected runs when the user selects a segment (by clicking it, or with Left / Right). index is the position after sorting. on_clear_selection runs when the user clears the selection (by clicking the empty part of the bar, or with Escape). Selecting the segment that is already selected does not raise the event again.

The events are raised only for user actions. Changing the selection from code with set_selected_segment, or removing the segments with clear, does not raise them.

Methods

Besides the Common methods for all Controls, a UsageChart also has the following additional methods:

MethodPurpose
add_segment(...)Appends one segment and repaints. A number is a solid segment with an empty label
add_segments(...)Appends several segments and repaints once
clear()Removes every segment, the selection and the tooltip, then repaints
set_flags(...)Replaces the flag set and repaints. The same flags again do nothing
set_sort_type(...)Sets the SortType and repaints
set_total_usage(...)Sets the TotalUsage and repaints
set_label_content(...)Sets the LabelContent of the tooltip
label_content()Returns the LabelContent of the tooltip
set_number_format(...)Sets the FormatNumber used for values in the tooltip
set_selected_segment(index)Selects the segment at index, or clears the selection when index is None. Does not raise an event

Key association

The following keys are processed by a UsageChart when it has focus. Segments that take no space are skipped, and the selection wraps around.

KeyPurpose
RightSelects the next segment (the first one if none is selected)
LeftSelects the previous segment (the last one if none is selected)
EscapeClears the selection

Mouse association

ActionPurpose
HoverShows the tooltip of the segment under the cursor. The text is chosen by LabelContent
Click on a segmentSelects that segment and raises on_usage_segment_selected when the selection changes
Click on the empty part of the barClears the selection and raises on_clear_selection when a segment was selected

Example

The following window shows a 256 GB drive. The unused part of the bar stays empty. Hovering a segment shows its label and its share of 256 GB. Selecting a segment writes its index into the window title.

A small demo is in examples/usagechart. A larger editor (flags, sort, total, tooltip, number format and the segments themselves) is in examples/usagechart_editor.

use appcui::prelude::*;

#[Window(events = UsageChartEvents)]
struct MyWin {
    chart: Handle<UsageChart>,
}

impl MyWin {
    fn new() -> Self {
        let mut win = MyWin {
            base: window!("'Usage chart',a:c,w:70,h:10,flags:Sizeable"),
            chart: Handle::None,
        };
        win.chart = win.add(usagechart!(
            "d:f,
             flags: Margins+DimSegmentsOnSelection,
             total: Fixed(256),
             nf: {dec, suffix: ' GB'},
             lc: LabelAndPercentage,
             segments: [
                {48, label: 'OS', fill: Solid},
                {36, label: 'Apps', fill: CrossHatch},
                {22, label: 'Documents', fill: Grid},
                {22, label: 'Media', fill: DiagonalDown},
                {8, label: 'Cache', fill: GridDouble}
             ]"
        ));
        win
    }
}

impl UsageChartEvents for MyWin {
    fn on_usage_segment_selected(&mut self, _: Handle<UsageChart>, index: usize) -> EventProcessStatus {
        self.set_title(&format!("Usage chart - segment {index}"));
        EventProcessStatus::Processed
    }

    fn on_clear_selection(&mut self, _: Handle<UsageChart>) -> EventProcessStatus {
        self.set_title("Usage chart");
        EventProcessStatus::Processed
    }
}

fn main() -> Result<(), appcui::system::Error> {
    App::new().window(MyWin::new).run()
}