PBToolboxAI v5 ← Site

chartpie — u_pbt_chartpie #

← Component reference · Guide contents

Pie chart: a pie, a doughnut or a half wheel. Each angle is a share of the whole — the legend hides a slice, the others take its room.

▶ See it live — Demo application, Pie chart tile: the preview, the code behind it and this page, side by side.


In brief #

Userobjectu_pbt_chartpie
Item classn_pbt_chartpie_slice (of_item(key))
Used forShowing how a whole is shared out — sales by region, tickets by status, budget used
PrincipleYou add slices (a key, a name, a value); the angles, the percentages, the legend and the animation are ours

Quick start #

// One slice per region : a key, a label and a value
uo_pie.of_add_slice(/*key*/ "north", /*label*/ "North", /*value*/ 420)
uo_pie.of_add_slice(/*key*/ "south", /*label*/ "South", /*value*/ 310)
uo_pie.of_add_slice(/*key*/ "east", /*label*/ "East", /*value*/ 260)

// The share of each region, written on its slice
uo_pie.ib_data_labels = true
uo_pie.is_label_format = "#percent#"

A slice is a SHARE of the whole #

You give values, never angles: the component adds up the slices shown and gives each the angle of its share of that sum. Hiding a slice — with a click in the legend, or through ib_visible — closes it in place: the others share the wheel again, and #percent#, id_percent and of_total() follow. A negative value has no angle: it is drawn as zero and reads back as you gave it.

Angles are counted in degrees, 0 at the top, in the drawing direction: ii_start_angle = -90 and ii_sweep_angle = 180 give the upper half of a circle — a gauge. The wheel is fitted to the room that angle takes: a half wheel is drawn twice as large.


Properties #

PropertyTypeDefaultRole
is_titlestring""Chart title, above the wheel. Markup accepted
ib_show_legendbooleantrueShows the legend. Clicking an entry hides its slice and raises ue_slice_toggled
is_legend_locationstringendWhere the legend goes (LEGEND_* constants): beside the wheel (END, START, in a column) or above and below it (TOP, BOTTOM, side by side). START/END are logical: they follow the reading direction. An unknown place is end, and reads back as such
ib_legend_valuesbooleanfalseWrites the value and the share of each slice in the legend, after its name
ib_value_tooltipbooleantrueThe panel that names the slice under the pointer — or the keyboard's — with its value and share. It is not is_tooltip, the tooltip of the whole component. The tooltip of a slice (its is_tooltip) is written in this panel, even at false
ib_data_labelsbooleanfalseOne label per slice, saying what is_label_format says, where is_label_position puts it. In a slice too narrow, the label is dropped rather than written over its neighbours
is_label_formatstring""What a label says: #value# the value, #percent# its share of the whole, #label# its name (without markup), #key# its key, #total# the total; #participation# says what #percent# says. The rest is written as it stands. Empty = the value alone
is_label_positionstringinsideWhere the labels go (LABEL_* constants): INSIDE, on the slice, in black or white whichever reads over it; OUTSIDE, beside the wheel, joined to their slice by a line and spread so that none sits on another
id_inner_radiusdouble0The hole, as a fraction of the radius: 0 a pie, 0.6 a doughnut (clamped to 0..0.9). The wheel slides from one to the other
is_center_textstring""Text in the hole of a doughnut, markup accepted. Placeholders: #total# the total of the slices shown (in the number format), #count# their number; #hover_label#, #hover_value#, #hover_percent# the slice under the pointer or the keyboard's — at rest, the whole: Total, the total, 100 %; #percent:key# and #value:key# the share and the value of one slice, kept up to date whatever the values do (a gauge writes its own "72 %"). An unknown key writes nothing. Nothing is written in a pie
ii_start_angleinteger0Where the first slice starts, in degrees: 0 at the top, 90 on the right, -90 on the left (450 reads back 90)
ii_sweep_angleinteger360How much of the circle the slices share, in degrees (clamped to 10..360). With ii_start_angle = -90, 180 draws the upper half — a gauge
ib_clockwisebooleantrueThe slices follow one another clockwise, or the other way. The keyboard arrows follow the drawing order
is_sortstring"none"The drawing order (SORT_* constants): NONE the order the slices were given, DESC the largest first, ASC the smallest first. The wheel, the legend and the keyboard follow it; the model keeps its order — of_get_values, of_count and the palette colours do not move. An unknown value is none
id_hover_push_outdouble0.08How far the slice under the pointer — or the keyboard's — slides out, as a fraction of the radius (clamped to 0..0.4). Adds to the slice's own id_push_out. 0 = nothing moves
ib_track_hoverbooleanfalseOpt-in for ue_slice_hovered: an event raised at every move of the pointer is only sent to an application that asked for it
is_formatstring""The format of every number the chart writes — labels, legend, value panel, centre text — in the syntax of a DataWindow format: #,##0.00, $#,##0, #,##0 "units", sections positive;negative;zero. The separators are those of the display language. Empty (or a format that is not a numeric one) = the number as it is, to the thousandth. The shares (#percent#) are always written to the tenth
id_others_thresholddouble0Gathers the small slices: every slice whose share of the whole is below this percentage joins an "Others" slice, drawn last in a neutral grey — the legend shows it instead of them. The members are judged on every slice, shown or hidden: hiding a big slice never pulls the small ones out, and the largest slice never joins. Its key is OTHERS_KEY ("__others") in every event, its value is the sum of its members shown, of_others_keys names them, of_item(OTHERS_KEY) reads its value and share or hides it. One small slice alone keeps its own wedge. 0 = never
ii_others_topinteger0Gathers by count: the N largest slices keep their wedge, every other one joins "Others" (at least two, as for the threshold). Works with id_others_threshold: a slice joins as soon as either rule says so. 0 = no such rule
is_others_labelstring""The name of the "Others" slice. Markup accepted in the legend and the value panel; the data labels and the summary read to a screen reader write its plain text. Empty = the word of the display language (Others, Autres…)
ib_auto_heightbooleanfalseThe component reports its ideal height and the userobject fits it (ue_auto_height). The width drives it: a half wheel is half as tall
ib_enabledbooleantrueGreyed out, the chart still shows what it shows but stops answering: no click, no double click, no key, no legend entry, and it leaves the Tab order
ib_animatedbooleantrueThe wheel arrives animated — it opens from its start, slice after slice — and new values slide: the edges between the slices move together, the wheel never shows a gap nor a slice over its neighbour; a removed slice closes at its place. A workstation set to reduce motion never animates, and the wheel lands in the same place
ii_anim_durationinteger450Duration of the arrival and of every movement, in milliseconds (clamped to 0..5000)
is_anim_easingstringease-outThe curve of the movement (EASING_* constants, the names of LiveCharts2): back-out goes past then comes back, bounce-out bounces, elastic-out snaps
ii_anim_delayinteger40On arrival only (a wheel that had nothing on screen), the delay between two slices, in milliseconds (0 to 1000): the wheel opens one slice after another, never more than 600 ms later in all, however many slices it has. 0 = all together. A change of values always slides every slice together
is_theme_stylestring""Visual style of the component (THEME_STYLE_* constants); empty = the application's, followed at every change
is_theme_modestring""Light or dark variant (THEME_MODE_* constants); empty = the application's, followed at every change
il_theme_accentlong-1Accent colour of this component (-1 = the theme's accent)
is_tooltipstring""Plain tooltip shown when hovering the component
is_super_tooltip_titlestring""Title of the rich tooltip (takes precedence over is_tooltip)
is_super_tooltip_textstring""Text of the rich tooltip (rich markup accepted)
is_super_tooltip_imagestring""Image of the rich tooltip

Methods #

MethodRole
of_add_slice ( string as_key, string as_label, double ad_value { , long al_color } )Adds a slice: its key, its name (markup accepted) and its value; the overload with a colour sets it, without it the palette chooses (one set for a light theme, one for a dark one). Returns 0 once added, -5 when the key is empty, holds / or `, starts with __ (the keys of the chart itself, such as OTHERS_KEY) or is **already held** (an add never replaces a slice), -2` when the component is not created
of_clear ( )Removes every slice — their handles go with them — and forgets the rows of of_from_datastore: a later of_map or of_reload does not bring them back. The settings stay, so do the roles of of_map; an empty wheel is drawn in their place. Returns 0 once cleared, -2 when the component is not created
of_from_datastore (datastore ads)Feeds the chart from a DataStore: one row = one slice. A column plays a role when it is named like it (key, label, value, color) or when of_map says so; a ROLE_VALUE column is needed. Without a ROLE_KEY column the key of a slice is its row number; with one, the rows that share a key are summed into one slice (the name and colour of the first) — a year of sales becomes one slice per category, without a loop. Numbers and dates are read as the DataWindow writes them on the workstation (decimal comma, regional dates: a date becomes yyyy-mm-dd). A second load updates the slices in place: a key that comes back keeps what the user and your code gave it (hidden from the legend, pushed out, a colour of the code, its tooltip) and its handle; the slices of keys that left go; the slices you added yourself stay, after those of the rows. Returns 0 once loaded — a row whose key is empty, holds / or `, starts with __ or is held by a slice you added is skipped, and of_get_last_error says how many —, -5 when the DataStore is not valid, no column plays ROLE_VALUE or a column of_map named is not in it (of_get_last_error names it), -2` when the component is not created
of_from_datastore (datawindow adw)The same from the rows of a DataWindow — the one the user sees beside the chart. Its data is shared with a scratch DataStore: a DataWindow that cannot share it is refused (-5, with the reason). Returns 0 once loaded, -5 as above, -2 when the component is not created
of_from_datastore (datawindowchild adwc)The same from the rows of a DataWindowChild (a drop-down DataWindow, a nested report). Returns 0 once loaded, -5 as above, -2 when the component is not created
of_get_last_error ( )Why the last call was refused (a role of of_map the rows do not have…), or how many rows the last of_from_datastore skipped and why; else the last error of the DLL, as for every component
of_get_values ( ref string as_keys[], ref double ad_values[] )Fills both arrays with the slices the chart holds now, in the order they were given (is_sort only changes the drawing), and returns their number — what the chart reads, not what was last sent
of_item ( string as_key )The n_pbt_chartpie_slice handle of one slice, to read or change its name, value, colour, push-out or visibility. of_item(OTHERS_KEY) is always valid: the "Others" slice, its value, its share, its visibility (of_has does not name it: it is the chart's, not yours). A key the chart does not hold gives a handle that changes nothing and reads empty values — a property cannot answer -5: check a key that comes from outside with of_has first
of_map ( string as_role, string as_column )Gives a column a role when its name does not say it: of_map(ROLE_VALUE, "amount"). Roles: ROLE_KEY (the rows of one key are summed), ROLE_LABEL, ROLE_VALUE, ROLE_COLOR (an RGB() long or #rrggbb). Before of_from_datastore, or after: the rows are then read again in place. The column is judged by the next of_from_datastore (-5 there when it does not hold it); when the rows already given do not hold it, they are not read again — the role waits for the rows you are about to give, and of_get_last_error says so. An empty column gives the role back to the column named like it. Returns 0 once set, -5 when the role is not one of the ROLE_ constants, -2 when the component is not created
of_others_keys ( ref string as_keys[] )Fills the array with the keys of the slices gathered into "Others" right now (see id_others_threshold and ii_others_top), in the order they were given, and returns their number — 0 when no slice is gathered. What a click on OTHERS_KEY is about
of_reload ( )Reads the rows of the last of_from_datastore again — after a Retrieve, after an edit — and updates the slices in place, as a second of_from_datastore does. Returns 0 once read, -5 when no rows were given (or of_clear forgot them, or the DataStore was destroyed), -2 when the component is not created
of_remove_slice ( string as_key )Removes one slice; the others take its room, its handle is destroyed. Returns 0 once removed, -5 when no slice has this key, -2 when the component is not created
of_set_values ( string as_keys[], double ad_values[] )New values for several slices in one call — what a dashboard calls on every refresh: the edges between the slices slide together to their new angles, the wheel never opens a gap. Returns 0 once applied, -5 when the two arrays differ in length or a key is not held (then nothing is applied), -2 when the component is not created
of_total ( )The total of the slices shown — what 100 % means right now: a slice hidden from the legend is not counted, nor a negative value
of_reset ( )Empties the chart — slice handles included — and puts every property back to its default. Returns 0 once done, -2 when the component is not created

Events #

EventFired when
ue_slice_clicked (string as_key, double ad_value)A slice was clicked — or confirmed with Enter or Space on the keyboard's slice (Tab to the chart, then the arrows): its key and its value — OTHERS_KEY and the sum of its slices for the "Others" slice. The click is judged where the slice rests: near the centre, the gap a pointed slice leaves as it slides out is still its own. A hidden slice has nothing to click
ue_slice_dblclicked (string as_key, double ad_value)Double click on a slice: its key and its value — the place to open the rows behind it (a drill down: a second of_from_datastore with the rows of that slice). The two clicks before it raised ue_slice_clicked, as in a DataWindow; Enter stays a click
ue_slice_rclicked (string as_key, long al_x, long al_y)Right click on a slice, or the Menu key (Shift+F10) on the keyboard's slice. al_x, al_y: where, in screen pixels — the pointer, or the middle of the slice for the Menu key, never wherever the mouse rests. A contextmenu component opens there: uo_menu.of_show(al_x, al_y)
ue_slice_toggled (string as_key, boolean ab_visible)The user hid or restored a slice with a click in the legend (or Enter / Space on its entry: the legend is one Tab stop, the arrows walk it). The last slice can be hidden too: the wheel then says All slices are hidden. What the chart displays is then no longer what the application sent. Setting ib_visible is a value, like ticking a check box: this event reports the user's gesture
ue_slice_hovered (string as_key)With ib_track_hover only: the pointer or the keyboard reaches another slice — its key; an empty key when it leaves them all: the pointer out of the wheel, Escape, the focus that leaves the chart
ue_auto_height (long al_height)With ib_auto_height: the userobject has just been resized to its ideal height

Item properties #

Read and written on the handle of a slice: uo_pie.of_item(/*key*/ "north").id_value = 450. Every read asks the chart.

PropertyTypeDefaultRole
is_labelstring""The name of the slice, as the legend and the panel show it; empty = its key
id_valuedouble0Its value; its angle is its share of the total, and it slides to it
il_colorlong-1Its colour; -1 = the palette colour. The inherited item colours (il_accent, il_back_color, il_text_color and their hover twins) have no effect on a slice: il_color decides
id_push_outdouble0Pushes it out of the wheel to single it out, as a fraction of the radius (clamped to 0..0.4)
ib_visiblebooleantrueHides the slice without removing it: the others share the wheel again, the legend keeps a way back
id_percentdouble0Its share of what the wheel shows, from 0 to 100; 0 when it is hidden (read only). Each share is written to the tenth: three equal slices read 33.3 % each, 99.9 % together — the shares are never forced to add up to 100
is_tooltipstring""Its tooltip, written in the panel that names the slice under the pointer (or the keyboard's) — never a second bubble. With ib_value_tooltip at false, the panel still opens for it, without the figures. Markup accepted
is_super_tooltip_titlestring""The bold title of its tooltip, written in the panel under its name
is_super_tooltip_textstring""The text of its tooltip, in the panel. Markup accepted
is_super_tooltip_imagestring""An image at the top of its tooltip, in the panel (bundle path, absolute path or resources.dll:name)

Examples #

A doughnut and its total #

// A hole of 60 % of the radius : a doughnut
uo_pie.id_inner_radius = 0.6

// In the hole : the slice under the pointer, in bold above its name ;
// at rest, the total of the slices shown above the word Total
uo_pie.is_center_text = "[b]#hover_value#[/b][br]#hover_label#"

// Whole numbers with a thousands separator : a display format, as in a DataWindow
uo_pie.is_format = "#,##0"

A half wheel as a gauge #

// From the left, over half a circle : the upper half
uo_pie.ii_start_angle = -90
uo_pie.ii_sweep_angle = 180
uo_pie.id_inner_radius = 0.62

// What is spent and what is left, in the colour they mean
uo_pie.of_add_slice(/*key*/ "spent", /*label*/ "Spent", /*value*/ 72, /*color*/ RGB(/*red*/ 15, /*green*/ 108, /*blue*/ 189))
uo_pie.of_add_slice(/*key*/ "left", /*label*/ "Remaining", /*value*/ 28, /*color*/ RGB(/*red*/ 150, /*green*/ 150, /*blue*/ 150))

// The share spent, in the hole : worked out by the chart, it follows every new value
uo_pie.is_center_text = "[b]#percent:spent#[/b][br]spent"

A live dashboard #

// Timer event of the window : new values for the same slices
// Local variables
string ls_keys[] = {"s1", "s2", "s3"}
double ld_load[]

// Read the figures, then hand them to the slices already on the chart : they
// SLIDE to their new angles instead of replaying their arrival
ld_load = wf_read_load()
uo_pie.of_set_values(/*keys*/ ls_keys, /*values*/ ld_load)

Opening the rows behind a slice #

// Event ue_slice_dblclicked of uo_pie : the key of the slice is the region
wf_open_orders(/*region*/ as_key, /*amount*/ ad_value)

A menu on a slice #

// Event ue_slice_rclicked of uo_pie : (string as_key, long al_x, long al_y)
// Remember the slice, then open the contextmenu of the window where the gesture
// was made : the pointer, or the middle of the slice for the Menu key
is_menu_slice = as_key
uo_menu.of_show(/*x*/ al_x, /*y*/ al_y)

From a DataStore #

// Local variables
long ll_rc

// One slice per category : the rows of a category are SUMMED into its slice
uo_pie.of_map(/*role*/ u_pbt_chartpie.ROLE_KEY, /*column*/ "category")
uo_pie.of_map(/*role*/ u_pbt_chartpie.ROLE_LABEL, /*column*/ "category")
uo_pie.of_map(/*role*/ u_pbt_chartpie.ROLE_VALUE, /*column*/ "amount")

// ONE call reads every row of the DataStore retrieved when the window opened
// (an instance variable), no loop
ll_rc = uo_pie.of_from_datastore(/*ads*/ ids_sales)

// A row with no category is skipped : the chart says how many
if uo_pie.of_get_last_error() <> "" then wf_status(uo_pie.of_get_last_error())

The DataWindow on screen, read again in place #

// Open event of the window : the chart reads the DataWindow the user sees
uo_pie.of_map(/*role*/ u_pbt_chartpie.ROLE_KEY, /*column*/ "region")
uo_pie.of_map(/*role*/ u_pbt_chartpie.ROLE_VALUE, /*column*/ "amount")
uo_pie.of_from_datastore(/*adw*/ dw_sales)
// After a Retrieve : the slices follow IN PLACE - a region the user hid from
// the legend stays hidden, a new region gets its slice
dw_sales.Retrieve()
uo_pie.of_reload()

The small slices in "Others" #

// Every slice under 5 % of the total joins ONE slice, drawn last in grey,
// and only the six largest products keep a slice of their own
uo_pie.id_others_threshold = 5
uo_pie.ii_others_top = 6
uo_pie.is_others_label = "Other products"

// The largest first : the reader compares them from the top
uo_pie.is_sort = u_pbt_chartpie.SORT_DESC
// Event ue_slice_clicked of uo_pie : a click on Others opens the slices it holds
// Local variables
string ls_keys[]

// Others names the slices it gathered
if as_key = uo_pie.OTHERS_KEY then
	uo_pie.of_others_keys(/*keys*/ ls_keys)
	wf_open_products(/*keys*/ ls_keys)
end if

Good practice #

Inherited from the common base #

These members exist on every visual component — they are not specific to this one. They are detailed once, in the transverse chapters; this table only says where to read them.

MembersRoleDetailed in
of_count · of_keys_at · of_hasWalk what the component holds3.2 Items
of_resetPut the component back to zero3.6 Resetting a component: of_reset()
of_register_shortcut · of_clear_shortcutsThe component's keyboard chords3.5 Keyboard shortcuts
of_is_created · of_is_ready · of_get_last_errorWhether it was born, whether it is ready, what failed3.7 Diagnostics
of_save_as_png · of_save_as_jpgExport the rendering as an image3.8 Exporting the rendering as an image
of_set_redrawGroup changes into a single repaint3.10 Best practices
of_preload_iconsIcons shown with no delayInstant display: of_icon
of_set_translationTranslate one of the component's labels5.2 Adapting a label: of_set_translation
of_focus_webviewGive the component the focus6.4 Keyboard and focus
of_print · of_print_to_pdfPrint, or write a PDF6.9 Printing
of_set_property · of_get_property · of_component_nameDriving a property by its name3.1 The property engine
is_accessible_name · is_accessible_descriptionThe name and description a screen reader says3.4 Optional properties and events

Two helpers are not inherited: of_icon and of_escape_markup live on n_pbt_utils. Declare one — n_pbt_utils lnv_utils, nothing to create — and call them on it.

← Component reference · Guide contents