diff --git a/docs/data_collection/api/datacollection_add_method.md b/docs/data_collection/api/datacollection_add_method.md index 3573ef27..967af1fb 100644 --- a/docs/data_collection/api/datacollection_add_method.md +++ b/docs/data_collection/api/datacollection_add_method.md @@ -52,8 +52,10 @@ component.data.add([ @descr: +When data is [grouped](data_collection/api/datacollection_group_method.md), DataCollection recalculates the [counters and aggregated values](data_collection/api/datacollection_group_method.md#group-counters-and-aggregates) of the groups over the resulting data. + +**Related article**: [Controls and operations](window/customization.md#controls-and-operations) + **Related sample**: [Data. Add](https://snippet.dhtmlx.com/ktd8ks0m) @changelog: The possibility to pass an array of items is added in v6.1. - -[comment]: # (@related:window/customization.md#controls-and-operations) diff --git a/docs/data_collection/api/datacollection_filter_method.md b/docs/data_collection/api/datacollection_filter_method.md index 456a8a9b..1efaa24a 100644 --- a/docs/data_collection/api/datacollection_filter_method.md +++ b/docs/data_collection/api/datacollection_filter_method.md @@ -65,8 +65,14 @@ grid.data.filter({ @descr: +**Related sample**: [Data. Filter](https://snippet.dhtmlx.com/csiwq3kj) + +### Combining filters + Unless `config.add` is set, the method replaces the currently applied filters; calling it without a rule at all drops all non-permanent filters and restores the unfiltered order. Permanent filters are the exception: they always survive and are reapplied first. The new rule then narrows their result further, so an item remains in the result only if it matches both the permanent filter and the new rule. -**Related sample**: [Data. Filter](https://snippet.dhtmlx.com/csiwq3kj) +### Filtering grouped data + +When data is [grouped](data_collection/api/datacollection_group_method.md), DataCollection matches the rule against the data items only. It never checks group headers and summary rows against the rule, so a filtering function never receives a `$group` or a `$groupSummary` item. A group stays as long as any of its items match the rule, and DataCollection recalculates the [counters and aggregated values](data_collection/api/datacollection_group_method.md#group-counters-and-aggregates) of the groups that remain. diff --git a/docs/data_collection/api/datacollection_group_method.md b/docs/data_collection/api/datacollection_group_method.md index 151e94af..808b913b 100644 --- a/docs/data_collection/api/datacollection_group_method.md +++ b/docs/data_collection/api/datacollection_group_method.md @@ -31,6 +31,7 @@ interface IGroupOrder { type TGroupOrder = string | TGroupOrderFunc | IGroupOrder; interface IGroupConfig { showMissed?: boolean | string; // true by default + showEmptyGroups?: boolean; // false by default field?: string; // "group" by default } @@ -48,7 +49,7 @@ group(order: TGroupOrder[], config?: IGroupConfig): void; config - (object) optional, the configuration of data grouping. The configuration object may include the following properties: + (object) optional, the configuration of data grouping. The configuration object may include the following properties: @@ -130,4 +131,66 @@ grid.data.group(["city"], { @descr: -@changelog: added in v9.0 \ No newline at end of file +## Group counters and aggregates + +Group headers follow the data they hold. DataCollection recalculates them after every change of the collection content, that is after the [`filter()`](data_collection/api/datacollection_filter_method.md), [`resetFilter()`](data_collection/api/datacollection_resetfilter_method.md), [`add()`](data_collection/api/datacollection_add_method.md), [`remove()`](data_collection/api/datacollection_remove_method.md), [`update()`](data_collection/api/datacollection_update_method.md) and [`parse()`](data_collection/api/datacollection_parse_method.md) methods. + +:::note +The same applies to TreeCollection, and thus to Grid in the [TreeGrid mode](grid/treegrid_mode.md): a counter covers the whole subtree of a header row, and an emptied header row disappears together with everything below it. +::: + +Recalculation needs no configuration, it happens on every data change while the collection is grouped: + +~~~jsx +const data = new dhx.DataCollection(); +data.parse(dataset); + +data.group([{ by: "status", map: { total: ["price", "sum"] }, summary: "bottom" }]); + +// the header row of the "wip" group, which holds two items with the total of 50 +const wip = data.map(item => item).find(item => item.$group); + +wip.$count; // 2 +wip.$totalCount; // 2 +wip.total; // 50 + +data.filter({ + by: "price", + match: 30, + compare: (value, match) => Number(value) >= Number(match) +}); + +wip.$count; // 1 +wip.$totalCount; // 2, the unfiltered number of items +wip.total; // 30, recomputed over the items that are left +data.getItem(`${wip.id}:summary`).total; // 30, the summary row follows + +data.resetFilter(); +wip.$count; // 2 +~~~ + +### Counters of a group + +A group header row carries the following service properties: + +- `$count` - the number of data items that the group currently holds. For a nested grouping it is the size of the whole subtree of the group. Nested headers and summary rows don't count as data +- `$totalCount` - the number of data items that the group holds ignoring the active filters. It equals `$count` when no filter is active + +### Aggregated fields + +DataCollection recomputes every field listed in the `map` object of a grouping level over the items that are left, on the header row and on the group summary row that the `summary` property adds alike, so both rows show the same values. + +### Filtering grouped data + +DataCollection matches a filtering rule against the data items only, so a filtering function never receives a `$group` or a `$groupSummary` item. A group stays as long as any of its items match the rule, and a group that loses all of them leaves the collection together with its summary row and its nested groups. [`map()`](data_collection/api/datacollection_map_method.md) skips such a group and [`getLength()`](data_collection/api/datacollection_getlength_method.md) leaves it out, unless you pass the `showEmptyGroups: true` config to the method, and [`resetFilter()`](data_collection/api/datacollection_resetfilter_method.md) brings it back either way. + +### Removing a group + +A group emptied by [`remove()`](data_collection/api/datacollection_remove_method.md) has no filter to bring it back, so it leaves the collection for good, its summary row included, and [`getItem()`](data_collection/api/datacollection_getitem_method.md) returns *undefined* for the id of its header. + +**Related sample**: [Grid. Grouping counters and empty groups](https://snippet.dhtmlx.com/f4a5voun?mode=wide) + +@changelog: +- As of v9.4, DataCollection recalculates the counters and aggregated values of group headers after every change of the collection content +- The `showEmptyGroups` property of the `config` parameter is added in v9.4 +- Added in v9.0 \ No newline at end of file diff --git a/docs/data_collection/api/datacollection_parse_method.md b/docs/data_collection/api/datacollection_parse_method.md index 011618ee..0048abd7 100644 --- a/docs/data_collection/api/datacollection_parse_method.md +++ b/docs/data_collection/api/datacollection_parse_method.md @@ -46,4 +46,6 @@ Please note that if you specify the `id` fields in the data collection, their va The method resets the applied sorting and filtering: the sorting is dropped, and so are all the filters except those applied with `permanent: true`, which are reapplied to the new data. +When data is [grouped](data_collection/api/datacollection_group_method.md), DataCollection recalculates the [counters and aggregated values](data_collection/api/datacollection_group_method.md#group-counters-and-aggregates) of the groups over the new data. + **Related sample**: [Data. Parse](https://snippet.dhtmlx.com/0zrxtmvi) diff --git a/docs/data_collection/api/datacollection_remove_method.md b/docs/data_collection/api/datacollection_remove_method.md index 9c940ff6..6914c411 100644 --- a/docs/data_collection/api/datacollection_remove_method.md +++ b/docs/data_collection/api/datacollection_remove_method.md @@ -20,6 +20,8 @@ component.data.remove(["2", "4"]); @descr: -**Related sample**: [Data. Remove](https://snippet.dhtmlx.com/ugdlqgp5) +When data is [grouped](data_collection/api/datacollection_group_method.md), passing the id of a group header removes the whole group: the header itself, the items of the group, its summary row and its nested groups. DataCollection recalculates the [counters and aggregated values](data_collection/api/datacollection_group_method.md#group-counters-and-aggregates) of the groups that remain. + +**Related article**: [Controls and operations](window/customization.md#controls-and-operations) -[comment]: # (@related:window/customization.md#controls-and-operations) +**Related sample**: [Data. Remove](https://snippet.dhtmlx.com/ugdlqgp5) diff --git a/docs/data_collection/api/datacollection_resetfilter_method.md b/docs/data_collection/api/datacollection_resetfilter_method.md index ba138bb6..f1953ff9 100644 --- a/docs/data_collection/api/datacollection_resetfilter_method.md +++ b/docs/data_collection/api/datacollection_resetfilter_method.md @@ -36,6 +36,8 @@ component.data.resetFilter({ id: "filter_id" }); @descr: +When data is [grouped](data_collection/api/datacollection_group_method.md), DataCollection recalculates the [counters and aggregated values](data_collection/api/datacollection_group_method.md#group-counters-and-aggregates) of the groups over the restored data and brings back the groups that the filter left with no items. + **Related sample**: - [Data. ResetFilter](https://snippet.dhtmlx.com/jg8wxfvc) - [Grid. ResetFilter](https://snippet.dhtmlx.com/15trblk2) \ No newline at end of file diff --git a/docs/data_collection/api/datacollection_update_method.md b/docs/data_collection/api/datacollection_update_method.md index dc5cab7f..0cdbc9bb 100644 --- a/docs/data_collection/api/datacollection_update_method.md +++ b/docs/data_collection/api/datacollection_update_method.md @@ -36,6 +36,8 @@ itemsForUpdate.forEach((item, index) => { }); ~~~ -**Related sample**: [Data. Update](https://snippet.dhtmlx.com/4g90gi6b) +When data is [grouped](data_collection/api/datacollection_group_method.md), DataCollection recalculates the [counters and aggregated values](data_collection/api/datacollection_group_method.md#group-counters-and-aggregates) of the groups over the resulting data. + +**Related article**: [Controls and operations](window/customization.md#controls-and-operations) -[comment]: # (@related:window/customization.md#controls-and-operations) +**Related sample**: [Data. Update](https://snippet.dhtmlx.com/4g90gi6b) diff --git a/docs/grid/api/grid_getsummary_method.md b/docs/grid/api/grid_getsummary_method.md index 52bc5d74..4ee097bf 100644 --- a/docs/grid/api/grid_getsummary_method.md +++ b/docs/grid/api/grid_getsummary_method.md @@ -56,6 +56,8 @@ console.log(columnSummary); //{ totalPopulation: 1000000, avgAge: 28 } - the val - When called without parameters, the method returns an object with the calculated values defined in the configuration of the component. - When the `id` parameter is passed to the method, it returns an object with the calculated values defined in the column's configuration together with the calculated values defined in the component's configuration. +In a grid with [grouped data](grid/usage.md#grouping-data), the method calculates the returned values over the data rows only: the group header rows and the group summary rows don't count as data. + **Related article:** [Getting the summary object](grid/configuration.md#getting-the-summary-object) **Related API**: [summary](grid/api/grid_summary_config.md) diff --git a/docs/grid/api/grid_group_config.md b/docs/grid/api/grid_group_config.md index 404d2816..7561e9a4 100644 --- a/docs/grid/api/grid_group_config.md +++ b/docs/grid/api/grid_group_config.md @@ -18,10 +18,11 @@ Note that when you initialize Grid with the `group` configuration property, the #### Usage -~~~jsx {22} +~~~jsx {25} type TAggregate = "sum" | "count" | "min" | "max" | "avg" | string; interface IGroupOrder { + by: string | ((row: IRow) => string); map?: { [field: string]: [string, TAggregate] | ((row: IRow[]) => string | number) }; summary?: "top" | "bottom"; } @@ -33,6 +34,8 @@ interface IGroup { panelHeight: number; // 40 by default hideableColumns?: boolean; // true by default showMissed?: boolean | string; // true by default + showEmptyGroups?: boolean; // false by default + counter?: boolean | ((row: IRow) => string); // true by default fields?: { [colId: string]: IGroupOrder }; order?: IGroupOrderItem[]; column?: string | ICol; @@ -60,6 +63,13 @@ You can find the detailed description of the `group` object properties with exam - if set to *true*, the rows that don't have values for grouping are rendered row by row after all the data - if a *string* value is set, e.g. "Missed", the rows that don't have values for grouping are rendered as a separate group the name of which will have the specified string value. This group will be rendered as the last one - if set to *false*, the rows that don't suit the grouping criteria won't be rendered +- `showEmptyGroups` - (optional) specifies whether a group that loses all its rows to filtering stays in the grid, *false* by default + - if set to *false*, such a group leaves the view together with its summary row and its nested groups, and [`resetFilter()`](data_collection/api/datacollection_resetfilter_method.md) brings it back + - if set to *true*, such a group remains visible with the `$count: 0` value and emptied aggregates: the "sum" and "count" aggregations give *0*, while "avg", "min" and "max" give *null*, as described in the [Data calculation functions](helpers/data_calculation_functions.md#aggregating-an-empty-set-of-items) guide +- `counter` - (optional) defines the text rendered next to the group name in the column with grouped data, *true* by default + - if set to *true*, Grid renders the current number of rows of the group in brackets, e.g. *(2)* + - if set to *false*, Grid renders only the group name + - if set to a *function*, it takes the group header row as a parameter and returns the string to render. Grid inserts the returned value as HTML, so it may contain markup; an empty string renders no counter. The row gives access to the `$count`, `$totalCount` and `$by` service properties and to every aggregated field of the `map` object of the level - `fields` - (optional) predefines an extended configuration for data grouping by certain columns, by setting the rules of aggregation and rendering of the results. The attributes of the `fields` object correspond to the ids of columns for which the aggregation rules and the order of results are being configured. The configuration of a column is defined by the `IGroupOrder` object that has the following properties: - `map` - (optional) an object for data aggregation in a group, where the keys are field names, and the values can be: - a tuple `[string, TAggregate]` that specifies the field and the aggregation type ("sum", "count", "min", "max", "avg") from the [`dhx.methods`](helpers/data_calculation_functions.md) helper @@ -69,6 +79,7 @@ You can find the detailed description of the `group` object properties with exam - a string that represents a grouping field - a function `((row: IRow) => string)` for dynamic defining of a group - an `IGroupOrder` object that has the following properties: + - `by` - the field name or a function `((row: IRow) => string)` for user-defined grouping - `map` - (optional) an object for data aggregation in a group, where the keys are field names, and the values can be: - a tuple `[string, TAggregate]` that specifies the field and the aggregation type ("sum", "count", "min", "max", "avg") from the `dhx.methods` helper - a user-defined aggregation function `((row: IRow[]) => string | number)` @@ -94,4 +105,8 @@ const grid = new dhx.Grid("grid_container", { **Related article**: [Grouping data](grid/usage.md#grouping-data) -@changelog: added in v9.0 \ No newline at end of file +**Related sample**: [Grid. Grouping counters and empty groups](https://snippet.dhtmlx.com/f4a5voun?mode=wide) + +@changelog: +- The `counter` and `showEmptyGroups` properties are added in v9.4 +- Added in v9.0 \ No newline at end of file diff --git a/docs/grid/api/grid_summary_config.md b/docs/grid/api/grid_summary_config.md index b02a8dc9..14fe6631 100644 --- a/docs/grid/api/grid_summary_config.md +++ b/docs/grid/api/grid_summary_config.md @@ -86,5 +86,13 @@ console.log(summary); // { totalPopulation: 1000000, totalArea: 50000, density: **Related API**: [getSummary()](grid/api/grid_getsummary_method.md) +#### Summaries in a grouped grid + +In a grid with [grouped data](grid/usage.md#grouping-data), Grid calculates the summaries over the data rows only: the group header rows and the group summary rows don't count as data. + +#### Summaries of an empty grid + +When a grid has no rows, Grid calls the built-in functors with an empty set of rows: the "sum" and "count" functors give *0*, while "avg", "min" and "max" give *null*, which renders as an empty value. Check the details in the [Data calculation functions](helpers/data_calculation_functions.md#aggregating-an-empty-set-of-items) guide. + @changelog: - Added in v9.0 \ No newline at end of file diff --git a/docs/grid/configuration.md b/docs/grid/configuration.md index 59a04076..1adc2f67 100644 --- a/docs/grid/configuration.md +++ b/docs/grid/configuration.md @@ -1212,6 +1212,10 @@ It is also possible to [get the object with the calculated values](#getting-the- Use the [`dhx.methods`](helpers/data_calculation_functions.md) helper to define the default statistical functions and to create custom functions for data calculation while creating the summary list. ::: +:::note +In a grid with [grouped data](grid/usage.md#grouping-data), the summaries are calculated over the data rows only: the group header rows and the group summary rows aren't counted as data. +::: + ### Column summary To form a summary list that will be available at the column's level only, you should use the [`summary`](grid/api/api_gridcolumn_properties.md) configuration option of the column. The `summary` configuration option of a column can be initialized either as an *object* or as a *string*. As an object it contains calculated values set as *key:value* pairs, where the *keys* are the field names and *values* can be: diff --git a/docs/grid/usage.md b/docs/grid/usage.md index 67984238..cf1591bc 100644 --- a/docs/grid/usage.md +++ b/docs/grid/usage.md @@ -310,6 +310,8 @@ grid.data.filter({ Unless `config.add` is set, the method replaces the currently applied filters; calling it without a rule at all drops all non-permanent filters and restores the unfiltered order. Permanent filters are the exception: they always survive and are reapplied first. The new rule then narrows their result further, so an item remains in the result only if it matches both the permanent filter and the new rule. +When grid data is [grouped](#grouping-data), the rule is matched against the data rows only, while the group headers and the summary rows are kept or dropped by what is left inside them. The counters and the aggregated values of the remaining groups are [recalculated](#group-counters-and-aggregates). + **Related sample**: [Grid. Basic filter](https://snippet.dhtmlx.com/g0zpjqi1) ### Sorting data @@ -722,7 +724,6 @@ It is possible to [set a predefined Grid configuration](#configuring-data-groupi :::info important - Data grouping isn't intended for working with [`lazyDataProxy`](grid/data_loading.md#dynamic-loading) -- Modifying the values of grouped elements won't modify the aggregated values - You mustn't change the order of elements grouping by drag-n-drop ::: @@ -873,6 +874,58 @@ const grid = new dhx.Grid("grid_container", { **Related sample:** [Grid. Grouping missing data](https://snippet.dhtmlx.com/0geopa0v) +- `showEmptyGroups` - (optional) specifies whether a group that loses all its rows to filtering stays in the grid, *false* by default + - if set to *false*, such a group leaves the view together with its summary row and its nested groups, and [`resetFilter()`](data_collection/api/datacollection_resetfilter_method.md) brings it back + - if set to *true*, such a group remains visible with the `$count: 0` value and emptied aggregates: the "sum" and "count" aggregations give *0*, while "avg", "min" and "max" give *null*, as described in the [Data calculation functions](helpers/data_calculation_functions.md#aggregating-an-empty-set-of-items) guide + +~~~jsx {8-10} +const grid = new dhx.Grid("grid_container", { + columns: [ + { id: "status", header: [{ text: "Status" }] }, + { id: "price", header: [{ text: "Price" }] } + ], + group: { + order: [{ by: "status", map: { price: ["price", "sum"] } }], + // the groups that lose all their rows after filtering + // stay in the grid with the zero count and the zero total + showEmptyGroups: true + }, + data: dataset +}); + +grid.data.filter({ + by: "price", + match: 40, + compare: (value, match) => Number(value) >= Number(match) +}); +~~~ + +**Related sample:** [Grid. Grouping counters and empty groups](https://snippet.dhtmlx.com/f4a5voun?mode=wide) + +- `counter` - (optional) defines the text rendered next to the group name in the column with grouped data, *true* by default + - if set to *true*, Grid renders the current number of rows of the group in brackets, e.g. *(2)* + - if set to *false*, Grid renders only the group name + - if set to a *function*, it takes the group header row as a parameter and returns the string to render. Grid inserts the returned value as HTML, so it may contain markup; an empty string renders no counter. The row gives access to the `$count`, `$totalCount` and `$by` service properties and to every aggregated field of the `map` object of the level + +The counter is a part of the default template of the column with grouped data, so Grid ignores it when the [`column`](#configuration-of-the-column-property-of-the-group-object) object carries a custom `template`. The default tooltip of that column shows the counter as well, and a custom `tooltipTemplate` drops it there in the same way. + +~~~jsx {8-9} +const grid = new dhx.Grid("grid_container", { + columns: [ + { id: "status", header: [{ text: "Status" }] }, + { id: "price", header: [{ text: "Price" }] } + ], + group: { + order: ["status"], + // e.g. "wip 1 of 2" + counter: (row) => `${row.$count} of ${row.$totalCount}` + }, + data: dataset +}); +~~~ + +**Related sample:** [Grid. Grouping counters and empty groups](https://snippet.dhtmlx.com/f4a5voun?mode=wide) + - `fields` - (optional) predefines an extended configuration for data grouping by certain columns, by setting the rules of aggregation and rendering of the results. The attributes of the `fields` object correspond to the ids of columns for which the aggregation rules and the order of results are being configured. The configuration of a column is defined by the `IGroupOrder` object that has the following properties: - `map` - (optional) an object for data aggregation in a group, where the keys are field names, and the values can be: - a tuple `[string, TAggregate]` that specifies the field and the aggregation type ("sum", "count", "min", "max", "avg") from the [`dhx.methods`](helpers/data_calculation_functions.md) helper @@ -920,6 +973,7 @@ b) the total rows under the grouped values set by the `summary` property - a string that represents a grouping field - a function `((row: IRow) => string)` for dynamic defining of a group - an `IGroupOrder` object that has the following properties: + - `by` - the field name or a function `((row: IRow) => string)` for user-defined grouping - `map` - (optional) an object for data aggregation in a group, where the keys are field names, and the values can be: - a tuple `[string, TAggregate]` that specifies the field and the aggregation type ("sum", "count", "min", "max", "avg") from the `dhx.methods` helper - a user-defined aggregation function `((row: IRow[]) => string | number)` @@ -1130,6 +1184,69 @@ column: { Note that the `column` object of the `group` configuration option has some properties of a Grid column. You can check the descriptions of the group column object properties enumerated above in the [Grid column properties](grid/api/api_gridcolumn_properties.md) guide. +### Group counters and aggregates + +Group headers follow the data they hold. Grid recalculates them after every change of the collection content, that is after the [`filter()`](data_collection/api/datacollection_filter_method.md), [`resetFilter()`](data_collection/api/datacollection_resetfilter_method.md), [`add()`](data_collection/api/datacollection_add_method.md), [`remove()`](data_collection/api/datacollection_remove_method.md), [`update()`](data_collection/api/datacollection_update_method.md) and [`parse()`](data_collection/api/datacollection_parse_method.md) methods of DataCollection. + +In the snippet below the [`counter`](#configuring-data-grouping) function renders the current number of rows of a group against the initial one, while the `map` object puts the recalculated total of the group into the "price" cell of the header row and of the summary row: + +~~~jsx {8-15} +const grid = new dhx.Grid("grid_container", { + columns: [ + { id: "status", header: [{ text: "Status" }] }, + { id: "price", header: [{ text: "Price" }] } + ], + group: { + order: ["status"], + fields: { + status: { + map: { price: ["price", "sum"] }, + summary: "bottom" + } + }, + // e.g. "wip 1 of 2" + counter: (row) => `${row.$count} of ${row.$totalCount}` + }, + data: dataset +}); + +grid.data.filter({ + by: "price", + match: 30, + compare: (value, match) => Number(value) >= Number(match) +}); +~~~ + +After the filtering above a group renders the number of rows that passed the filter, while `$totalCount` keeps the unfiltered number of rows of the group. + +**Related sample:** [Grid. Grouping counters and empty groups](https://snippet.dhtmlx.com/f4a5voun?mode=wide) + +#### Counters of a group + +A group header row carries the following service properties: + +- `$count` - the number of data rows that the group currently holds. For a nested grouping it is the size of the whole subtree of the group. Nested headers and summary rows don't count as data +- `$totalCount` - the number of data rows that the group holds ignoring the active filters. It equals `$count` when no filter is active +- `$by` - the field that the level groups by: the field name, or the function passed as `by` when the level groups by a function + +Every row of a grid, a group header included, also carries the `$index` service property with the position of the row among the rendered ones. + +#### Aggregated fields + +Grid recomputes every field listed in the `map` object of a grouping level over the rows that are left, on the header row and on the group summary row that the `summary` property adds alike, so both rows show the same values. + +Grid calculates the [summaries](grid/configuration.md#custom-statistics-in-the-column-headerfooter-and-spans) of a column and of the grid over the data rows only as well, so the group header rows and the group summary rows don't affect the totals. + +#### Filtering grouped data + +Grid matches a filtering rule (or a filtering function) against the data rows only. It never checks group headers and summary rows against the rule, so a custom filtering callback never receives a `$group` or a `$groupSummary` row. A group stays as long as any of its rows match the rule. + +A group that loses all its rows to filtering leaves the grid together with its summary row and its nested groups, and comes back when you reset the filter. To keep such a group in the grid, set the [`showEmptyGroups`](grid/api/grid_group_config.md) property of the `group` configuration object to *true*. + +#### Removing a group + +Calling the [`remove()`](data_collection/api/datacollection_remove_method.md) method with the id of a group header removes the whole group: the header itself, the rows of the group, its summary row and its nested groups. + ### Making group panel elements closable You can enable closing of all the elements of the group panel using the [`closable`](grid/api/grid_closable_config.md) configuration option of Grid. @@ -1204,6 +1321,9 @@ The method takes the following parameters: - if set to *true*, the rows that don't have values for grouping are rendered row by row after all the data - if a *string* value is set, e.g. "Missed", the rows that don't have values for grouping are rendered as a separate group the name of which will have the specified string value. This group will be rendered as the last one - if set to *false*, the rows that don't suit the grouping criteria won't be rendered + - `showEmptyGroups` - (optional) specifies whether a group that loses all its rows to filtering stays in the grid, *false* by default + - if set to *false*, such a group leaves the view together with its summary row and its nested groups, and [`resetFilter()`](data_collection/api/datacollection_resetfilter_method.md) brings it back + - if set to *true*, such a group remains visible with the `$count: 0` value and emptied aggregates - `field` - (optional) the group field name, *"group"* by default There are several examples of grouping Grid data via the `group()` method of DataCollection: diff --git a/docs/helpers/data_calculation_functions.md b/docs/helpers/data_calculation_functions.md index 258fbf40..effaa1da 100644 --- a/docs/helpers/data_calculation_functions.md +++ b/docs/helpers/data_calculation_functions.md @@ -11,6 +11,18 @@ The following functors are available: - `min` - calculates the minimal value in the data - `sum` - calculates the sum of data values +Each functor takes a set of items and the name of the field to calculate. The `sum` and `count` functors always return a number, while `avg`, `min` and `max` return *null* when there is nothing to calculate: + +~~~ts +const methods: { + sum: (items: IDataItem[], field: string) => number; + count: (items: IDataItem[], field: string) => number; + avg: (items: IDataItem[], field: string) => number | null; + min: (items: IDataItem[], field: string) => number | null; + max: (items: IDataItem[], field: string) => number | null; +}; +~~~ + For example, this is how the `sum` functor is called: ~~~jsx @@ -18,6 +30,20 @@ const rows = [{ value: 10 }, { value: 20 }, { value: 30 }]; const sum = dhx.methods.sum(rows, "value"); // 60 ~~~ +### Aggregating an empty set of items + +Called with an empty or missing set of items, `sum` and `count` return *0*, while `avg`, `min` and `max` return *null*: + +~~~jsx +dhx.methods.sum([], "value"); // 0 +dhx.methods.count([], "value"); // 0 +dhx.methods.avg([], "value"); // null +dhx.methods.min([], "value"); // null +dhx.methods.max([], "value"); // null +~~~ + +A *null* value renders as an empty cell, so an `avg`, `min` or `max` cell with nothing to calculate stays empty. This is what the footer of a grid that has no rows shows, as well as the aggregates of a group that is kept in a grid by the [`showEmptyGroups`](grid/api/grid_group_config.md) property. + ### Defining a custom functor You can specify a custom function for calculating data. For example, you can use the `methods` helper function for adding custom calculations to [get a summary of counted values](grid/configuration.md#getting-the-summary-object).