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:

  1. Base class grouping specified using base class grouping specification through a grouping rule.
  2. Direct class grouping specified using groupByClass attribute at specification level.
  3. Property grouping specified using property grouping specification through a grouping rule.
  4. Display label grouping specified using groupByLabel attribute at specification level.
  5. 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,
            },
          ],
        },
      ],
    },
  ],
};

Example of using "condition" attribute

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",
        },
      ],
    },
  ],
};

Example of using "priority" attribute

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",
        },
      ],
    },
  ],
};

Example of using "onlyIfNotHandled" attribute

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" },
            },
          ],
        },
      ],
    },
  ],
};

Example of using "base class" attribute

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,
            },
          ],
        },
      ],
    },
  ],
};

Example of using "create group for single item" attribute

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,
            },
          ],
        },
      ],
    },
  ],
};

Example of using "create group for single item" attribute

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
Example of using "create group for unspecified values" attribute set to "false" Example of using "create group for unspecified values" attribute set to "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.getNodesIterator({ imodel, rulesetOrId: ruleset }).then(async (x) => collect(x.items));
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",
                },
              ],
            },
          ],
        },
      ],
    },
  ],
};

Example of using "ranges" attribute

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 or hideExpression 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"
Example of using "application stage" attribute set to "query" Example of using "application stage" attribute set to "post-process"

Last Updated: 03 April, 2024