documentationfor yFiles for HTML 2.6

Tables

Many application areas require a presentation of data where the nodes of a diagram are organized in a tabular way, i.e., where each node is associated to a specific row and column in the grid-like structure of a table. Swimlane layouts are a popular example of such presentations.

The following figure shows samples of tabular presentations of diagrams:

Tabular data presentation
Swimlane layout with four lanes from top to bottom
A diagram following the Business Process Modeling Notation (BPMN) where swimlanes are additionally subdivided by so-called milestones

The yFiles for HTML diagramming library contains comprehensive support for tabular data presentation, which builds on the general concept of grouped graphs. The provided functionality covers the “look” as well as the “feel,” i.e., table structures with rows and columns can be both rendered and also interactively edited. Rows and columns can be:

  • added,
  • removed,
  • resized, and also
  • reordered.

Furthermore, the hierarchical layout style, more precisely class HierarchicLayout, provides advanced support for automatic tabular layout.

Concepts

The presentation of a diagram in a tabular way needs an additional element, namely a table structure to encompass the proper nodes of the diagram. This table structure is backed by a group node, and the diagram’s proper nodes, i.e., the actual content nodes, need to be set up so that they are child nodes of this group node.

Using a group node to hold the content nodes brings all advantages of this concept as described in Grouping Nodes. For example, when the group node is moved, the child nodes move accordingly, thus maintaining the visual clue that they are contained in the table group node.

The table structure, which typically shows some rows and columns within which the content nodes lie, directly results from the table model which actually defines these rows and columns.

The group node that is “behind” the table structure uses a special node style implementation to govern the actual rendering. The visualization of individual rows and columns can be conveniently achieved by using special style implementations.

User interaction with the table structure is supported by a specialized input mode, which makes available support for resizing rows and columns, re-parenting them, or editing their labels, for example.

It is crucial to understand, however, that the content nodes of a diagram and the table structure are only loosely coupled. More precisely, the association of a content node to a row and a column is only done on a geometric basis, i.e., the node’s center coordinates determine the row (column) that it belongs to.

This also means that when a content node is moved, for example, the representation of the table does not change in any way.

Table Model

Basically, the table model represents the blueprint for the rendering of the table structure. The table model defines the rows and columns of a table, their sizes, and in particular their nesting structure, i.e., rows (columns) may have so-called child rows (columns) to support nested row (column) structures.

Table structure with nested rows and columns shows a table structure with nested rows and columns. The table has two top-level rows where one consists of two child rows. Similarly, there are two top-level columns where one consists of two child columns.

Note that there are no content nodes.

Table structure with nested rows and columns
Table structure with nested rows and columns

The table model is constituted by the types listed in Table model types.

Table model types
Type nameDescription
ITableProvides the virtual roots of the row and column hierarchies of a table model. The Table class is the default implementation.
IStripeModels common aspects of rows and columns in a table model. This includes the layout, insets, (minimum) sizes, for example, and also support for labels.
IRowModels a row in a table model. Note that IStripe is the super interface.
IColumnModels a column in a table model. Note that IStripe is the super interface.

The ITable implementation provides the virtual roots of the row and column hierarchies of a table model. These allow to add top-level rows and columns, to which in turn child rows and child columns can be added to.

The area that belongs to a row (column) is determined by its height (width) as well as by its position with respect to the order of rows (columns). A row (column) spans all columns (rows) of the table structure, i.e., its width (height) is the accumulated width (height) of all columns (rows).

While the width of a row (height of a column) is determined implicitly, its height (width) can be set. This size, however, is restricted by the row’s (column’s) minimum size. In other words, a row (column) cannot be made smaller than its minimum size.

setSize(stripe: IStripe, size: number): void
Sets the preferred size for a given IStripe, i.e., the height of a row or the width of a column.

Also, if the row (column) contains any nested child rows (columns), setting the size does not take effect, since it is implicitly constrained to the accumulated sizes of the nested rows (columns).

For the same reason the actual bounds, or layout, of a row (column) also cannot be set explicitly. Instead, they are determined as a result of the table structure, which means they are calculated anew through the row’s (column’s) layout property whenever necessary.

Insets in a table structure

From a geometric point of view, a table consists of its content area, which is determined by the geometry of its rows and columns, and the surrounding border area. The border area is determined by the table insets, and may be used to display a table description, for example.

insets
Sets the table’s insets. The insets define a border around the rows and columns of a table structure.

Similarly, rows and columns also have a content area and a border area. The content area contains the content nodes of the row (column), respectively the content area of nested rows (columns).

The border area which is determined by corresponding row (column) insets, however, denotes both extra space outside of a row’s (column’s) content area as well as some padding inside the content area.

setStripeInsets(stripe: IStripe, insets: Insets): void
Sets insets for a given IStripe, i.e., a row or column. The insets denote extra space to the left and right of a row’s content area, respectively at the top and bottom of a column’s content area.The top and bottom values (row) as well as the left and right values (column) denote padding space at the inside of the content area.

In a parent row (column), i.e., a row (column) with nested child rows (columns), the content area of the child rows (columns) lies completely within the content area of the parent row (column).

The border area of the parent row (column) is to the left and right of its nested child rows (at the top and bottom of its nested child columns).

The actual insets and the actual size of a parent row (column) where nested child rows (columns) affect the parent row’s (column’s) preferred insets and size can be queried using the following methods:

actualInsets: Insets
actualSize: number
Convenience (read only) properties for interface IStripe to get the actual insets and the actual size of a row or column.

Unless explicitly set, insets for new rows and columns are adopted from the default values as specified by the rowDefaults/columnDefaults provided by the ITable instance:

rowDefaults
columnDefaults
Properties to manage the IStripeDefaults instances that hold default values for the rows and columns of a table structure. Among other things, the default values also include the default insets that are used for newly created rows and columns.

Working with the Table Model

Creating a table model that uses the default ITable implementation class Table goes as follows:

Creating a table model
// Creating a new table model using the default ITable implementation.
const table = new Table()

To actually get a table model rendered, it needs yet two other things: a group node, to which the table model needs to be bound to, and an appropriate node style implementation that can handle table models and that is set as the group node’s style.

Binding the table model to a group node means making the table model available in the group node’s look-up, so that it can be queried using the look-up mechanism:

const table = groupNode.lookup(ITable.$class)

This is important for properly handling user interaction with a table structure, for example.

Using class TableNodeStyle, the predefined node style which support tables, as the node style for a group node conveniently binds a table model to the group node as a side effect:

Associating a table model with a group node
const graph = getMyGraph()
const table = getMyTable()

const tableStyle = new TableNodeStyle(table)

// Create a top-level group node and bind, via the TableNodeStyle, the table to
// it.
graph.createGroupNode(null, table.layout.toRect(), tableStyle)

Upon initialization, a Table instance holds an empty model that has no rows or columns. Rows and columns can be created using the following Table methods:

createChildRow(owner: IRow, height: number, minHeight: number, insets: Insets, style: IStripeStyle, tag: Object, index: number): IRow
createChildColumn(owner: IColumn, width: number, minWidth: number, insets: Insets, style: IStripeStyle, tag: Object, index: number): IColumn
Creates a new row (column) as a child of the given IRow (IColumn).

Adding rows and columns illustrates a simple tabular presentation with two rows and columns together with its setup in code.

Adding rows and columns
A 2x2 table<para class="dguide-para"></para>
const graph = getMyGraph()

// Create a new table model and set insets on the table so that the rows and
// columns stand out.
const table = new Table()
table.insets = new Insets(10, 10, 10, 10)

const rowStyle = new ShapeNodeStyle({ fill: Fill.TRANSPARENT })
table.rowDefaults.style = new NodeStyleStripeStyleAdapter(rowStyle)
const columnStyle = new ShapeNodeStyle({
  fill: new SolidColorFill(new Color(139, 162, 220))
})
table.columnDefaults.style = new NodeStyleStripeStyleAdapter(columnStyle)
// Using no insets prevents column/row headers.
const insets = new Insets(0)
table.columnDefaults.insets = insets
table.rowDefaults.insets = insets
table.columnDefaults.size = 30
table.rowDefaults.size = 30

// Add top-level rows and columns. The table's size is then 2x2.
table.createRow()
table.createRow()
table.createColumn()
table.createColumn()

// Using TableNodeStyle to render the table structure.
const tableStyle = new TableNodeStyle(table)
const backgroundStyle = new ShapeNodeStyle({
  fill: new SolidColorFill(new Color(248, 236, 201))
})
tableStyle.backgroundStyle = backgroundStyle

// Create a top-level group node and bind, via the TableNodeStyle, the table to
// it.
graph.createGroupNode(null, table.layout.toRect(), tableStyle)

Rows and columns can be deleted using the following method defined by the ITable interface:

remove(stripe: IStripe): void
Removes a given IStripe, i.e., a row or column.

The following convenience methods support recursively deleting rows or columns and enable resizing of the remaining rows or columns:

removeWithResize(stripe: IStripe): void
removeRecursively(stripe: IStripe): void
removeRecursivelyWithResize(stripe: IStripe): void
Convenience (extension) methods for interface ITable to remove rows or columns.

Undo/Redo Support

Support for Undo/Redo operations for the tables in a diagram can be easily enabled using the functionality in class Table which is the default implementation of interface ITable:

installStaticUndoSupport(graph: IGraph): void
Enables Undo/Redo support for tables. Installs the undo support instance which is valid on the graph at the time this method is called.
installDynamicUndoSupport(graph: IGraph): void
Enables Undo/Redo support for tables. Registers the graph from which the undo support will be queried.

Enabling support for Undo/Redo operations for tables
const graph = getMyGraph()

if (graph != null) {
  // Enabling general undo support.
  graph.undoEngineEnabled = true
  // Using the undo support from the graph also for all future table instances.
  Table.installStaticUndoSupport(graph)
}

Visual Representation

A table model’s visual representation is provided by a special node style implementation that can handle table models, class TableNodeStyle.

Class TableNodeStyle can be set as the style for a group node as follows:

Setting a table node style with a group node
const graph = getMyGraph()
const table = getMyTable()

const tableStyle = new TableNodeStyle(table)

// Create a top-level group node and bind, via the TableNodeStyle, the table to it.
graph.createGroupNode(null, table.layout.toRect(), tableStyle)

The ITable instance that is set with the TableNodeStyle instance defines the style’s table model. When the TableNodeStyle instance is set as the style of a group node, this table model is used to render a table structure that has the group node’s dimensions and location.

As a side effect of setting the TableNodeStyle instance as the style for a group node, the table model is also conveniently bound to the group node. This means that it is made available in the group node’s look-up, so that it can be queried using the look-up mechanism:

const table = groupNode.lookup(ITable.$class)

This is important for properly handling user interaction with a table structure, for example.

Note that because of its table model, class TableNodeStyle does not support style sharing among multiple group nodes.

TableNodeStyle governs the overall rendering of a table structure. It uses a separate node style implementation to render the background behind all rows and columns:

backgroundStyle
Sets the node style that is used to render the background behind all rows and columns of a table structure.

Additionally, class TableNodeStyle defines the following property to determine the rendering order of rows and columns:

tableRenderingOrder
Sets the rendering order of the rows and column of a table structure. By default, columns are rendered first, then rows.

The rendering order also affects the fill colors for rows and columns. Using the default rendering order, for example, neither column fill colors nor border lines can be seen for non-transparent row fill colors.

The rendering order is also taken into account for hit-testing.

Rows and Columns

The visual representation of rows and columns of a table structure is conveniently provided by implementations. Stripe styles can be associated with IStripe instances either at creation time or using the following method defined in interface ITable:

setStyle(stripe: IStripe, style: IStripeStyle): void
Sets the style for an IStripe instance, i.e., the row or column of a table structure.

The TableNodeStyle that is set with the group node, delegates the rendering of rows and columns to these row and column styles.

Stripe Styles

Stripe styles are responsible for the graphical rendering of rows and columns of a table. Interface IStripeStyle is the common base type for actual implementations.

Predefined stripe style implementations lists the predefined stripe style implementations present in yFiles.

Predefined stripe style implementations
Type Name Description
NodeStyleStripeStyleAdapterAllows using a node style for the visual representation of a column or row.
StripeStyleBase<TVisual>Greatly simplifies stripe style customization using subclassing.
TemplateStripeStyleEnables using template styles for the visual representation of a stripe.

The following figures show custom IStripeStyle implementations that provide specialized rendering for rows and columns of table structures:

Custom row/column rendering

Rows and columns of a table structure can have labels whose position is determined by label models. The following method defined in ITable can be used to add labels to IStripe instances:

addLabel(owner: IStripe, text: string, layoutParameter: ILabelModelParameter, style: ILabelStyle, preferredSize: Size, tag: Object): ILabel
Adds a label to an IStripe instance, i.e., to a row or column of a table structure.

StretchStripeLabelModel and StripeLabelModel are specifically tailored to support positions for the labels of rows and columns in a table structure. It is used as the default label model when adding labels to an IStripe.

The following figure illustrates labels in a tabular presentation with a single row and a single column together with their setup in code:

Row and column labels
Row and column labels<para class="dguide-para"></para>
const graph = getMyGraph()
const table = getMyTable()

// Setup of row and column default insets.
table.rowDefaults.insets = new Insets(30, 5, 30, 5)
table.columnDefaults.insets = new Insets(5, 30, 5, 30)

// Create a single column.
const column = table.createColumn(100)
// Add and configure two labels for the column.
table.addLabel(column, 'Column North')
table.addLabel(column, 'Column South', StretchStripeLabelModel.SOUTH)

// Create a single row.
const row = table.createRow(100)
// Add and configure two labels for the row.
table.addLabel(row, 'Row West')
table.addLabel(row, 'Row East', StretchStripeLabelModel.EAST)

// Using TableNodeStyle to render the table structure.
const tableStyle = new TableNodeStyle(table)
const backgroundStyle = new ShapeNodeStyle({
  fill: new SolidColorFill(new Color(248, 236, 201))
})
tableStyle.backgroundStyle = backgroundStyle

// Create a top-level group node and bind, via the TableNodeStyle, the table to
// it.
graph.createGroupNode(null, table.layout.toRect(), tableStyle)

Note that row labels that use WEST are automatically rotated 90 degrees counterclockwise while row labels that use EAST are rotated 90 degrees clockwise.

NodeLabelModelStripeLabelModelAdapter can be used to use a node label model (e.g. FreeNodeLabelModel) as if the row or column were a node. This allows for positions where the dedicated stripe label models mentioned above do not suffice.

CSS Styling of Indicators

The different interactive editing gestures on the table provide visual indicators for better user feedback. Those indicators can either be styled by changing the template that is associated with the resource key (see DefaultStripeInputVisualizationHelper and Customizing String Resources) or through the CSS classes that are provided by the default templates (see Styling of Table Indicators).

User Interaction

TableEditorInputMode is a specialized input mode that brings additional support for handling mouse gestures and keyboard interaction specific to the tabular representation of a diagram.

The additional mouse gesture support covers, for example:

  • Selecting rows and columns,
  • resizing them, and
  • changing their position in the order of rows (columns).

TableEditorInputMode can be used in conjunction with GraphEditorInputMode } as shown in the following code snippet, or can be used on its own.

Enabling TableEditorInputMode to handle mouse gestures
const geim = getMyGraphEditorInputMode()

geim.add(new TableEditorInputMode())

Upon initialization, TableEditorInputMode creates and installs the input modes listed in the following table as concurrent input modes.

Input modes used by TableEditorInputMode
Type Name Description
ResizeStripeInputModeHandles resizing of rows and columns.
ReparentStripeInputModeRecognizes and handles row (column) re-parenting/reordering mouse gestures
ClickInputModeRecognizes mouse clicks, including double clicks. This child input mode is disabled if TableEditorInputMode is used as a child input mode of GraphEditorInputMode.
KeyboardInputModeRecognizes key events.
StripeDropInputModeHandles drag’n’drop operations of nodes onto the canvas. Facilitates convenient creation of new rows and columns, including visual feedback for a user. This child input mode is disabled if TableEditorInputMode is used as a child input mode of GraphEditorInputMode.
TextEditorInputModeHandles label editing. This child input mode is disabled if TableEditorInputMode is used as a child input mode of GraphEditorInputMode.

If used in conjunction with GraphEditorInputMode, the ClickInputMode, KeyboardInputMode, and TextEditorInputMode child input modes of TableEditorInputMode are disabled and the respective GraphEditorInputMode counterparts are enabled.

Each of the input modes can be obtained or replaced using a like-named property defined by TableEditorInputMode.

In order for TableEditorInputMode and its child input modes to properly recognize the connection between a table structure/table model and its group node, they rely on the table model being bound to the group node. The input modes query the group node’s look-up and expect to find a table model:

const table = groupNode.lookup(ITable.$class)

Interaction Customization

Class TableEditorInputMode provides properties and callbacks that allow for fine-grained control over the support for user interaction.

TableEditorInputMode customization lists the customization properties of class TableEditorInputMode. Most of the properties support that combinations of stripe types can be specified using the constants defined in the StripeTypes enumeration type.

TableEditorInputMode customization
Name Purpose
clickSelectableItemsA combination of StripeTypes that specifies whether rows and/or columns can be selected by mouse clicks.
selectableItemsA combination of StripeTypes that specifies whether rows and/or columns can be selected.
labelEditableItemsA combination of StripeTypes that specifies whether labels of rows and/or columns can be edited.
deletableItemsA combination of StripeTypes that specifies whether rows and/or columns can be deleted.
clickSelectableRegionsA combination of StripeSubregionTypes that specifies the set of sub regions of rows and columns that can be selected by mouse clicks.

Hit-testing

Hit-testing support in a table structure takes into account the overall rendering order of rows and columns (as defined by class TableNodeStyle) as well as the complexities introduced by nested rows and columns.

By default, the specific needs for hit-testing support in a table structure are provided by the StripeHitTester class. The actual instance that is used is also available in the group node’s look-up.

The following convenience methods in TableEditorInputMode help in finding which part(s) of a table structure is (are) underneath a given location. The StripeSubregion class encodes both the stripe, i.e., row or column, and the actual sub region within the stripe.

findStripe
Depending on the rendering order of rows and columns, returns the sub region of either a row or a column that is underneath the given location.
findStripes
Returns an ordered list of sub regions of (nested) rows and columns that are underneath the given location. The ordering takes into account the rendering order of rows and columns and also the nesting of child rows (columns).

The StripeSubregionTypes enumeration defines constants for the different sub regions within a stripe. The figure below depicts the default definitions of these sub regions together with their corresponding constants for the column of the table structure in Row and column labels.

The stripe sub regions for a row are defined analogously.

Stripe sub regions in a column
The column’s STRIPE sub region,
HEADER sub region,
LEADING_HEADER sub region,
TRAILING_HEADER sub region.

The hit-testing for the sub regions can be handled through dedicated IHitTestable implementations. The IStripeHitTestHelper interface bundles all stripe-related IHitTestable instances and makes the default implementations conveniently accessible.

Tutorial Demo Code

The Table Editor demo demonstrates user interaction and shows how to customize it. The demo also presents a way to create new rows and columns via drag’n’drop gestures.

Automatic Layout

Automatic layout for a diagram containing table structures that are modeled using ITable implementations can be conveniently invoked using LayoutExecutor which performs all necessary setup. Internally, LayoutExecutor uses the TableLayoutConfigurator class.

Using LayoutExecutor for automatic layout of table structures
const graphComponent = getMyGraphComponent()

// Configure HierarchicLayout.
const hl = new HierarchicLayout()
hl.componentLayoutEnabled = false
hl.layoutOrientation = LayoutOrientation.LEFT_TO_RIGHT
hl.orthogonalRouting = true
hl.recursiveGroupLayering = false

// Start layout.
try {
  const layoutExecutor = new LayoutExecutor({
    graphComponent,
    layout: hl,
    duration: '0.5s',
    updateContentRect: true
  })
  layoutExecutor.tableLayoutConfigurator.compaction = false
  layoutExecutor.start()
} catch (e) {
  console.log('Layout failed')
}

TableLayoutConfigurator converts relevant parts of the table structure into PartitionGrid-based information that is understood by algorithms which support automatic tabular layout. In particular, this information includes:

  • the geometric information of all rows and columns, i.e., the top and bottom borders, left and right borders, and insets, and also the
  • row and column that each content node belongs to.

Note that the row and column is determined geometrically, by the node’s center coordinates.

Tutorial Demo Code

The following tutorial demo applications show how to create table models using the Table class and how to use the TableNodeStyle node style implementation for tabular data representation:

  • The Table Editor demo demonstrates user interaction and shows how to customize it. The demo also presents a way to create new rows and columns via drag’n’drop gestures.