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 #
| Userobject | u_pbt_chartpie |
| Item class | n_pbt_chartpie_slice (of_item(key)) |
| Used for | Showing how a whole is shared out — sales by region, tickets by status, budget used |
| Principle | You 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 #
| Property | Type | Default | Role |
|---|---|---|---|
is_title | string | "" | Chart title, above the wheel. Markup accepted |
ib_show_legend | boolean | true | Shows the legend. Clicking an entry hides its slice and raises ue_slice_toggled |
is_legend_location | string | end | Where 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_values | boolean | false | Writes the value and the share of each slice in the legend, after its name |
ib_value_tooltip | boolean | true | The 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_labels | boolean | false | One 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_format | string | "" | 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_position | string | inside | Where 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_radius | double | 0 | The 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_text | string | "" | 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_angle | integer | 0 | Where the first slice starts, in degrees: 0 at the top, 90 on the right, -90 on the left (450 reads back 90) |
ii_sweep_angle | integer | 360 | How 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_clockwise | boolean | true | The slices follow one another clockwise, or the other way. The keyboard arrows follow the drawing order |
is_sort | string | "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_out | double | 0.08 | How 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_hover | boolean | false | Opt-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_format | string | "" | 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_threshold | double | 0 | Gathers 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_top | integer | 0 | Gathers 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_label | string | "" | 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_height | boolean | false | The 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_enabled | boolean | true | Greyed 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_animated | boolean | true | The 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_duration | integer | 450 | Duration of the arrival and of every movement, in milliseconds (clamped to 0..5000) |
is_anim_easing | string | ease-out | The 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_delay | integer | 40 | On 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_style | string | "" | Visual style of the component (THEME_STYLE_* constants); empty = the application's, followed at every change |
is_theme_mode | string | "" | Light or dark variant (THEME_MODE_* constants); empty = the application's, followed at every change |
il_theme_accent | long | -1 | Accent colour of this component (-1 = the theme's accent) |
is_tooltip | string | "" | Plain tooltip shown when hovering the component |
is_super_tooltip_title | string | "" | Title of the rich tooltip (takes precedence over is_tooltip) |
is_super_tooltip_text | string | "" | Text of the rich tooltip (rich markup accepted) |
is_super_tooltip_image | string | "" | Image of the rich tooltip |
Methods #
| Method | Role | |
|---|---|---|
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 #
| Event | Fired 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.
| Property | Type | Default | Role |
|---|---|---|---|
is_label | string | "" | The name of the slice, as the legend and the panel show it; empty = its key |
id_value | double | 0 | Its value; its angle is its share of the total, and it slides to it |
il_color | long | -1 | Its 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_out | double | 0 | Pushes it out of the wheel to single it out, as a fraction of the radius (clamped to 0..0.4) |
ib_visible | boolean | true | Hides the slice without removing it: the others share the wheel again, the legend keeps a way back |
id_percent | double | 0 | Its 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_tooltip | string | "" | 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_title | string | "" | The bold title of its tooltip, written in the panel under its name |
is_super_tooltip_text | string | "" | The text of its tooltip, in the panel. Markup accepted |
is_super_tooltip_image | string | "" | 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 #
- Few slices. Beyond six or seven, the eye no longer compares neighbouring angles: group the small ones into an "Others" slice (
id_others_threshold), or turn to the cartesian chart. - Order the slices from the largest to the smallest (
is_sort = SORT_DESC), starting at the top: reading follows the drawing direction. - Labels outside when the slices are narrow: they stay readable, joined to their slice, instead of being dropped.
- Turn the animation off on a refreshing dashboard, or keep it and call
of_set_values: the slices slide instead of starting their arrival again. - The chart keeps its drawing direction in a right-to-left application — the outside labels are written left to right too, away from the wheel; only the
START/ENDlegend and its arrows follow the reading direction. - A picture of the chart (
of_save_as_png, printing) is taken once the slide has landed: no need to turn the animation off first. - A colour that means something (late, spent) is set through
il_color; leave the palette to the rest, it changes with the light or dark theme.
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.
| Members | Role | Detailed in |
|---|---|---|
of_count · of_keys_at · of_has | Walk what the component holds | 3.2 Items |
of_reset | Put the component back to zero | 3.6 Resetting a component: of_reset() |
of_register_shortcut · of_clear_shortcuts | The component's keyboard chords | 3.5 Keyboard shortcuts |
of_is_created · of_is_ready · of_get_last_error | Whether it was born, whether it is ready, what failed | 3.7 Diagnostics |
of_save_as_png · of_save_as_jpg | Export the rendering as an image | 3.8 Exporting the rendering as an image |
of_set_redraw | Group changes into a single repaint | 3.10 Best practices |
of_preload_icons | Icons shown with no delay | Instant display: of_icon |
of_set_translation | Translate one of the component's labels | 5.2 Adapting a label: of_set_translation |
of_focus_webview | Give the component the focus | 6.4 Keyboard and focus |
of_print · of_print_to_pdf | Print, or write a PDF | 6.9 Printing |
of_set_property · of_get_property · of_component_name | Driving a property by its name | 3.1 The property engine |
is_accessible_name · is_accessible_description | The name and description a screen reader says | 3.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.