Grouping rule
TypeScript type: GroupingRule.
Grouping rules provide advanced ways to group instances when creating hierarchies.
It allows to define these types of groupings:
- Group by base class.
- Group by any property of the class by a common value or a range of values.
- Group multiple instances with the same label into one ECInstance node. This can be used in cases when these instances represent the same object for the user.
The rule works in conjunction with other grouping options available in hierarchy specifications: groupByClass
and groupByLabel
. All grouping rules are
applied in this priority:
- Base class grouping specified using base class grouping specification through a grouping rule.
- Direct class grouping specified using
groupByClass
attribute at specification level. - Property grouping specified using property grouping specification through a grouping rule.
- Display label grouping specified using
groupByLabel
attribute at specification level. - Same label grouping specified using same label instance grouping specification through a grouping rule.
The rule itself works in a similar way as hierarchy rules - rule identifies what to group and it has specifications which tell how the grouping should be done.
Attributes
Name | Required? | Type | Default |
---|---|---|---|
Filtering | |||
class |
Yes | SingleSchemaClassSpecification |
|
requiredSchemas |
No | RequiredSchemaSpecification[] |
[] |
condition |
No | ECExpression | "" |
priority |
No | number |
1000 |
onlyIfNotHandled |
No | boolean |
false |
Grouping | |||
groups |
Yes | GroupingSpecification[] |
Attribute: class
Specification of ECClass which should be grouped using this rule.
Type | SingleSchemaClassSpecification |
Is Required | Yes |
Attribute: condition
An ECExpression that results in a boolean value. If specified, the grouping rule applies only to instance nodes that cause the condition to evaluate to true
.
Type | ECExpression |
Is Required | No |
Default Value | "" |
// There's a hierarchy of `bis.Model` instances and their elements. In addition, there's a grouping rule for `bis.Element`
// that only takes effect if element's model has `IsPrivate` flag set to `true`.
const ruleset: Ruleset = {
id: "example",
rules: [{
ruleType: "RootNodes",
specifications: [{
specType: "InstanceNodesOfSpecificClasses",
classes: { schemaName: "BisCore", classNames: ["Model"], arePolymorphic: true },
groupByClass: false,
}],
}, {
ruleType: "ChildNodes",
condition: `ParentNode.IsOfClass("Model", "BisCore")`,
specifications: [{
specType: "RelatedInstanceNodes",
relationshipPaths: [{
relationship: { schemaName: "BisCore", className: "ModelContainsElements" },
direction: "Forward",
}],
groupByClass: false,
groupByLabel: false,
}],
customizationRules: [{
ruleType: "Grouping",
class: { schemaName: "BisCore", className: "Element" },
condition: `ParentNode.ECInstance.IsPrivate`,
groups: [{
specType: "Property",
propertyName: "CodeValue",
createGroupForSingleItem: true,
}],
}],
}],
};
Attribute: requiredSchemas
A list of ECSchema requirements that need to be met for the rule to be used.
Type | RequiredSchemaSpecification[] |
Is Required | No |
Default Value | [] |
// The ruleset has one root node rule that returns `bis.ExternalSourceAspect` instances. The
// ECClass was introduced in BisCore version 1.0.2, so the rule needs a `requiredSchemas` attribute
// to only use the rule if the version meets the requirement.
const ruleset: Ruleset = {
id: "example",
rules: [{
ruleType: "RootNodes",
requiredSchemas: [{ name: "BisCore", minVersion: "1.0.2" }],
specifications: [{
specType: "InstanceNodesOfSpecificClasses",
classes: [{
schemaName: "BisCore",
classNames: ["ExternalSourceAspect"],
}],
}],
}],
};
Attribute: priority
Controls the order in which specifications are handled — specification with higher priority value is handled first. If priorities are equal, the specifications are handled in the order they appear in the ruleset.
Type | number |
Is Required | No |
Default Value | 1000 |
// The ruleset has two root node rules that return nodes "A" and "B" respectively. The rules
// have different priorities and higher priority rule is handled first - it's node appears first.
const ruleset: Ruleset = {
id: "example",
rules: [{
ruleType: "RootNodes",
priority: 1,
specifications: [{
specType: "CustomNode",
type: "A",
label: "A",
}],
}, {
ruleType: "RootNodes",
priority: 2,
specifications: [{
specType: "CustomNode",
type: "B",
label: "B",
}],
}],
};
Attribute: onlyIfNotHandled
When true
, the rule takes effect only when all other grouping rules with higher priority are ruled out. This attribute is most useful for defining fallback rules.
Type | boolean |
Is Required | No |
Default Value | false |
// The ruleset has two root node rules that return nodes "A" and "B" respectively. The "A" rule has
// lower priority and `onlyIfNotHandled` attribute, which allows it to be overriden by higher priority rules.
// The "B" rule does exactly that.
const ruleset: Ruleset = {
id: "example",
rules: [{
ruleType: "RootNodes",
priority: 1,
onlyIfNotHandled: true,
specifications: [{
specType: "CustomNode",
type: "A",
label: "A",
}],
}, {
ruleType: "RootNodes",
priority: 2,
specifications: [{
specType: "CustomNode",
type: "B",
label: "B",
}],
}],
};
Attribute: groups
Specifies a list of grouping specifications which describe the kind of grouping that should be applied. There are 3 types of supported grouping:
Type | GroupingSpecification[] |
Is Required | Yes |
Grouping specifications
Base class grouping
Base class grouping allows grouping ECInstance nodes by their base class (as opposed to the hierarchy specifications' groupByClass
attribute, which
always groups by direct class).
Multiple levels of base class grouping may be constructed by specifying multiple rules for ECClasses in the same class hierarchy. In that case
the order of the rules has to match the order of the class hierarchy - from the most base class to the most derived one. If the rules can't be
defined in required order, the actual order may be adjusted using the priority
attribute.
Name | Required? | Type | Default |
---|---|---|---|
baseClass |
No | SingleSchemaClassSpecification |
Value of rule's class attribute |
createGroupForSingleItem |
No | boolean |
false |
Attribute: baseClass
Specification of the base ECClass to group by. If specified, allows grouping by a subclass of the class specified by rule's class
attribute.
Type | SingleSchemaClassSpecification |
Is Required | No |
Default Value | Value of rule's class attribute |
// The ruleset contains a root nodes rule for `bis.Element` instances and a grouping rule that puts
// all `bis.PhysicalElement` instances into a class group.
const ruleset: Ruleset = {
id: "example",
rules: [{
ruleType: "RootNodes",
specifications: [{
specType: "InstanceNodesOfSpecificClasses",
classes: { schemaName: "BisCore", classNames: ["Element"], arePolymorphic: true },
groupByClass: false,
groupByLabel: false,
}],
customizationRules: [{
ruleType: "Grouping",
class: { schemaName: "BisCore", className: "Element" },
groups: [{
specType: "Class",
baseClass: { schemaName: "BisCore", className: "PhysicalElement" },
}],
}],
}],
};
Attribute: createGroupForSingleItem
Specifies whether a grouping node should be created if there is only one item in that group.
Type | boolean |
Is Required | No |
Default Value | false |
// There's a root nodes rule that returns nodes for all `bis.Element` instances and there's a grouping rule
// that groups those elements by `CodeValue` property. The grouping rule has the `createGroupForSingleItem`
// flag, so property grouping nodes are created even if they group only a single element.
const ruleset: Ruleset = {
id: "example",
rules: [{
ruleType: "RootNodes",
specifications: [{
specType: "InstanceNodesOfSpecificClasses",
classes: { schemaName: "BisCore", classNames: ["Element"], arePolymorphic: true },
groupByClass: false,
}],
customizationRules: [{
ruleType: "Grouping",
class: { schemaName: "BisCore", className: "Element" },
groups: [{
specType: "Property",
propertyName: "CodeValue",
createGroupForSingleItem: true,
}],
}],
}],
};
Property grouping
Property grouping allows grouping by a property of the instance by value or by given ranges of values.
Property grouping nodes always appear under class grouping nodes (if any).
Multiple levels of property grouping may be constructed by specifying multiple rules. The order of grouping matches the order of grouping rules.
If the rules can't be defined in required order, the actual order may be adjusted using the priority
attribute.
Name | Required? | Type | Default |
---|---|---|---|
propertyName |
Yes | string |
|
createGroupForSingleItem |
No | boolean |
false |
createGroupForUnspecifiedValues |
No | boolean |
true |
imageId |
No | string |
"" |
ranges |
No | PropertyRangeGroupSpecification[] |
[] |
Attribute: propertyName
Name of the ECProperty which is used for grouping. The property must exist on the ECClass specified by the rule's class
attribute and it must be
of either a primitive or a navigation type.
Type | string |
Is Required | Yes |
Attribute: createGroupForSingleItem
Specifies whether a grouping node should be created if there is only one item in that group.
Type | boolean |
Is Required | No |
Default Value | false |
// There's a root nodes rule that returns nodes for all `bis.Element` instances and there's a grouping rule
// that groups those elements by `CodeValue` property. The grouping rule has the `createGroupForSingleItem`
// flag, so property grouping nodes are created even if they group only a single element.
const ruleset: Ruleset = {
id: "example",
rules: [{
ruleType: "RootNodes",
specifications: [{
specType: "InstanceNodesOfSpecificClasses",
classes: { schemaName: "BisCore", classNames: ["Element"], arePolymorphic: true },
groupByClass: false,
}],
customizationRules: [{
ruleType: "Grouping",
class: { schemaName: "BisCore", className: "Element" },
groups: [{
specType: "Property",
propertyName: "CodeValue",
createGroupForSingleItem: true,
}],
}],
}],
};
Attribute: createGroupForUnspecifiedValues
Should a separate grouping node be created for nodes whose grouping value is not set or is set to an empty string.
Type | boolean |
Is Required | No |
Default Value | true |
// The ruleset contains a root nodes rule for `bis.Element` instances and a grouping rule that groups them
// by `UserLabel` property. By default all nodes whose instance doesn't have a value for the property would
// be placed under a "Not Specified" grouping node, but the grouping rule has this behavior disabled through
// the `createGroupForUnspecifiedValues` attribute.
const ruleset: Ruleset = {
id: "example",
rules: [{
ruleType: "RootNodes",
specifications: [{
specType: "InstanceNodesOfSpecificClasses",
classes: { schemaName: "BisCore", classNames: ["Element"], arePolymorphic: true },
groupByClass: false,
}],
customizationRules: [{
ruleType: "Grouping",
class: { schemaName: "BisCore", className: "Element" },
groups: [{
specType: "Property",
propertyName: "UserLabel",
createGroupForUnspecifiedValues: false,
}],
}],
}],
};
createGroupForUnspecifiedValues: false |
createGroupForUnspecifiedValues: true |
---|---|
Attribute: imageId
Specifies grouping node's image ID. If set, the ID is assigned to Node.imageId and it's up to the UI component to decide what to do with it.
Type | string |
Is Required | No |
Default Value | "" |
// The ruleset contains a root nodes rule for `bis.Element` instances and a grouping rule that groups them
// by `UserLabel` property. The grouping rule also sets an image identifier for all grouping nodes.
const ruleset: Ruleset = {
id: "example",
rules: [{
ruleType: "RootNodes",
specifications: [{
specType: "InstanceNodesOfSpecificClasses",
classes: { schemaName: "BisCore", classNames: ["Element"], arePolymorphic: true },
groupByClass: false,
}],
customizationRules: [{
ruleType: "Grouping",
class: { schemaName: "BisCore", className: "Element" },
groups: [{
specType: "Property",
propertyName: "UserLabel",
imageId: "my-icon-identifier",
createGroupForSingleItem: true,
}],
}],
}],
};
// Confirm that all grouping nodes got the `imageId`
const nodes = await Presentation.presentation.getNodes({ imodel, rulesetOrId: ruleset });
expect(nodes).to.not.be.empty;
nodes.forEach((node) => {
expect(node).to.containSubset({
key: {
type: StandardNodeTypes.ECPropertyGroupingNode,
propertyName: "UserLabel",
},
imageId: "my-icon-identifier",
});
});
Attribute: ranges
Ranges into which the grouping values are divided. Instances are grouped by value if no ranges are specified.
Type | PropertyRangeGroupSpecification[] |
Is Required | No |
Default Value | [] |
Name | Required? | Type | Default | Meaning |
---|---|---|---|---|
fromValue |
Yes | string |
Value that defines the range start (inclusive) | |
toValue |
Yes | string |
Value that defines the range end (inclusive) | |
imageId |
No | string |
imageId of the property group specification |
Identifier of an image to use for the grouping node. |
label |
No | string |
"{from value} - {to value}" |
Grouping node's label. May be localized. |
Range [fromValue
, toValue
] is inclusive on both sides. If a value falls into more than one range, the first listed range that contains the value is chosen.
// The ruleset contains a root nodes rule for `bis.GeometricElement3d` and a grouping rule that groups them
// by `Yaw` property into 3 ranges: "Negative", "Positive" and "Zero".
const ruleset: Ruleset = {
id: "example",
rules: [{
ruleType: "RootNodes",
specifications: [{
specType: "InstanceNodesOfSpecificClasses",
classes: { schemaName: "BisCore", classNames: ["GeometricElement3d"], arePolymorphic: true },
groupByClass: false,
}],
customizationRules: [{
ruleType: "Grouping",
class: { schemaName: "BisCore", className: "GeometricElement3d" },
groups: [{
specType: "Property",
propertyName: "Yaw",
ranges: [{
fromValue: "0",
toValue: "0",
label: "Zero",
}, {
fromValue: "-360",
toValue: "0",
label: "Negative",
}, {
fromValue: "0",
toValue: "360",
label: "Positive",
}],
}],
}],
}],
};
Deprecated attribute: groupingValue
Specifies whether instances should be grouped using property's display or raw value.
Note: Grouping by property value is required if the display label is overridden to display grouped instances count.
Warning: Grouping by label and sorting by property value is not possible.
Display value should always be used for grouping. In cases when there's a need to show grouped instances count suffix, that can be achieved at the UI component layer by composing UI node's label from node's display label and GroupingNodeKey.groupedInstancesCount.
Type | "PropertyValue" | "DisplayLabel" |
Is Required | No |
Default Value | "DisplayLabel" |
Deprecated attribute: sortingValue
Specifies whether nodes should be sorted by their display label or the grouping property's value. In most cases the result is the same, unless a label override rule is used to change node's display label.
Note: Sorting by property value only makes sense when instances are grouped by property value as well.
Warning: Grouping by label and sorting by property value is not possible.
Type | "PropertyValue" | "DisplayLabel" |
Is Required | No |
Default Value | "DisplayLabel" |
Same label instance grouping
Allows grouping multiple instances with the same label into one ECInstances type of node. Similar to display label grouping, but instead of showing a grouping node with multiple grouped ECInstance nodes, it shows a single ECInstances node which represents multiple ECInstances.
Name | Required? | Type | Default |
---|---|---|---|
applicationStage |
No | "Query" | "PostProcess" |
"Query" |
Attribute: applicationStage
Grouping nodes by label is an expensive operation because it requires the whole hierarchy level to be created before even the first grouped node can be produced. To alleviate the performance impact when this specification is used, two applicationStage
settings have been introduced:
"Query"
groups instances during ECSql query, which can often make use of database indices and is generally fairly quick. It is chosen as the default option, however, it fails to produce grouping nodes when certain ruleset specifications are involved."PostProcess"
groups instances after the whole hierarchy level is built. It incurs a large performance penalty, but it will produce the expected result in all cases.
Type | "Query" | "PostProcess" |
Is Required | No |
Default Value | "Query" |
Choosing between "Query"
and "PostProcess"
Always prefer "Query"
unless any of the following conditions apply to the hierarchy level that is being grouped:
- The hierarchy level is built out of instances that do not share a common base class.
- The hierarchy level aggregates nodes that have been produced by more than one specification.
- The hierarchy level contains nodes that have been merged from a nested hierarchy level as a result of
hideNodesInHierarchy
orhideExpression
attributes.
On the other hand, "PostProcess"
can be applied to any hierarchy level, regardless of its composition, but at a cost to performance.
Example
// The ruleset contains a root nodes rule for `bis.InformationPartitionElement` and `bis.Model` instances. The grouping rules
// tells the rules engine to group them by label. `bis.InformationPartitionElement` and `bis.Model` classes have no common base class,
// so two different grouping rules are required to define this kind of grouping and that also means that `Query` type
// of grouping is not possible - grouping at `PostProcessing` step is required.
const ruleset: Ruleset = {
id: "example",
rules: [{
ruleType: "RootNodes",
specifications: [{
specType: "InstanceNodesOfSpecificClasses",
classes: {
schemaName: "BisCore",
classNames: ["InformationPartitionElement", "Model"],
arePolymorphic: true,
},
groupByClass: false,
groupByLabel: false,
}],
customizationRules: [{
ruleType: "Grouping",
class: { schemaName: "BisCore", className: "InformationPartitionElement" },
groups: [{
specType: "SameLabelInstance",
applicationStage: "PostProcess",
}],
}, {
ruleType: "Grouping",
class: { schemaName: "BisCore", className: "Model" },
groups: [{
specType: "SameLabelInstance",
applicationStage: "PostProcess",
}],
}],
}],
};
applicationStage: "Query" |
applicationStage: "PostProcess" |
---|---|
Last Updated: 24 January, 2023