Skip to main content

group()

groups data in a collection that has a plain tree-like structure according to the specified order and additional configuration

important

Data grouping isn't intended for working with lazyDataProxy

note

Grouped data can be serialized. After serialization data is available for rendering and editing as a plain tree-like structure

Usage​

type TGroupOrderFunc = (item: IDataItem) => string;
type TAggregate = "sum" | "count" | "min" | "max" | "avg" | string;
interface IGroupOrder {
by: string | TGroupOrderFunc;
map?: {
[field: string]: [string, TAggregate] | ((item: IDataItem[]) => string | number)
};
summary?: "top" | "bottom";
}
type TGroupOrder = string | TGroupOrderFunc | IGroupOrder;
interface IGroupConfig {
showMissed?: boolean | string; // true by default
showEmptyGroups?: boolean; // false by default
field?: string; // "group" by default
}

group(order: TGroupOrder[], config?: IGroupConfig): void;

Parameters​

order (array) an array that defines the order and configuration for data grouping. Each element in the array can be:
  • a string that represents a grouping field
  • a function (i: IDataItem) => string for dynamic defining of a group
  • an IGroupOrder object that has the following properties:
    • by: string | function - the field name or a function for user-defined grouping
    • map?: object - 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 (i: IDataItem[]) => string | number
    • summary?: string - optional, specifies where the total row is rendered - at the top or at the bottom of the group
config(object) optional, the configuration of data grouping. The configuration object may include the following properties:
  • showMissed?: boolean | string - optional, specifies whether the elements that don't have the field for grouping should be displayed, true by default
    • 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?: boolean - optional, specifies whether a group that loses all its items stays in the collection, false by default
    • if set to false, such a group leaves the collection together with its summary row and its nested groups, so map() skips it and getLength() leaves it out. A group emptied by filtering comes back after a resetFilter() call
    • if set to true, such a group stays with the $count: 0 value and emptied aggregates: the "sum" and "count" aggregations give 0, while "avg", "min" and "max" give null
  • field?: string - optional, the group field name, "group" by default

Examples​

  • simple grouping with the use of a callback function and a string field value
const grid = new dhx.Grid("grid_container", {
columns: [
{ id: "salary", header: [{ text: "Salary" }] },
{ id: "experience", header: [{ text: "Experience (years)" }] },
{ id: "city", header: [{ text: "City" }] }
],
group: true,
groupable: true,
data: dataset
});

grid.data.group([
function(row) {
if (row.salary < 30000) return "Low income";
if (row.salary >= 30000 && row.salary < 70000) return "Medium income";
return "High income";
},
"city"
]);
  • grouping with the use of a configuration object and aggregation settings
const grid = new dhx.Grid("grid_container", {
columns: [
{ id: "department", header: [{ text: "Department" }] },
{ id: "employees", header: [{ text: "Number of Employees" }] },
{ id: "location", header: [{ text: "Location" }] }
],
group: true,
groupable: true,
data: dataset
});

grid.data.group([{
by: "department", // grouping by the `department` field
map: {
employees: ["employees", "sum"], // aggregation: sums up the number of employees
location: (rows) => {
// a custom function for calculating unique locations
const uniqueLocations = [...new Set(rows.map(r => r.location))];
return uniqueLocations.join(", ");
}
},
summary: "top" // the total row is rendered at the top of the group
}]);
  • grouping with the use of the showMissed property
const grid = new dhx.Grid("grid_container", {
columns: [
{ id: "name", header: [{ text: "Name" }] },
{ id: "age", header: [{ text: "Age" }] },
{ id: "city", header: [{ text: "City" }] }
],
group: true,
groupable: true,
data: dataset
});

grid.data.group(["city"], {
// the group elements that don't have the "city" value
// will be rendered in the "Unknown City" group
showMissed: "Unknown City"
});

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(), resetFilter(), add(), remove(), update() and parse() methods.

note

The same applies to TreeCollection, and thus to Grid in the TreeGrid mode: 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:

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() skips such a group and getLength() leaves it out, unless you pass the showEmptyGroups: true config to the method, and resetFilter() brings it back either way.

Removing a group​

A group emptied by remove() has no filter to bring it back, so it leaves the collection for good, its summary row included, and getItem() returns undefined for the id of its header. If the data is grouped with the showEmptyGroups: true config, the group stays in the collection with the $count: 0 value instead.

Related sample: Grid. Group counter after filtering

Change log:

  • 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