documentationfor yFiles for HTML 2.6

Creating a simple Web Application

In this section we will create a very basic yFiles for HTML web application, a single page that contains a GraphComponent, the main component for displaying graphs.

The GraphComponent is the main Component in yFiles for HTML. It displays the graph and provides rich means of interaction with the diagram.

The web application will be implemented alongside the demos delivered with the package. It is therefore helpful to open the yFiles for HTML package in an IDE of your choice. The package includes pre-configured project settings for JetBrains IDEs and Visual Studio Code.

Creating the GUI

Before we begin, we recommend creating a folder inside the demos folder where you can put all the files that we are going to create (e.g. $PathToYfilesPackage/demos-js/MyFirstApp).

Start by creating a blank html page using a HTML5 page template.

Then, we add a simple empty div element to the page which will later show the component and give it a non-zero size via a CSS rule.

The GraphComponent does not have a minimum size configured by default. It will always use the size that has been assigned to the container DOM element and fill the space with the graph and UI. This means that you will have to make sure that the div has a non-zero size, because otherwise you might not see the graph at all.You can use any technique to assign a size to the element, and it may change in size, dynamically at any time. This works with CSS frameworks, dynamically sized container components, animations, etc.

yFiles for HTML consists of a set of JavaScript files which provide different functionality as described here.

In this example, we use import statements with symbolic names to load the JavaScript library files into the browser. Then, we create a separate javascript file (MyFirstApplication.js) which will be loaded via a <script> tag from our index.html page.

Demonstration of our first example application (our index.html and MyFirstApplication.js files)
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>yFiles for HTML Sample Web App</title>
    <!-- supply some sane defaults for the graph component and page -->
    <style>html, body, #graphComponent { width: 100%; height: 100%; padding: 0; margin: 0; }</style>
  </head>
  <body>
    <div id="graphComponent"></div>
    <script type="module" crossorigin="anonymous" src="MyFirstApplication.js"></script>
</body>
</html>
import { GraphComponent, License } from 'yfiles'

License.value = {
  /* <add your license information here> */
}

const graphComponent = new GraphComponent('#graphComponent')

For the symbolic names to be resolved to actual files, the application needs preprocessing, e.g. by the demo server that is included in the package. Please refer to the Demo Server section on how to set it up and to learn about alternatives.

A valid license needs to be present in order to use yFiles for HTML. In this introduction, we place the license data in our JavaScript source code file.However, in the demos, the license is placed in a separate file for the sake of simplicity. More details on how to load the license can be found in Licensing.

Module Loading

yFiles for HTML consists of several JavaScript modules. For example, the JS Module 'yfiles/view-component' provides the basic visualization infrastructure, styles and some basic support classes (e.g., collections, iterations etc). The name of the required module(s) of each type is available in the API documentation.

For details on the yFiles for HTML modules and how these can be loaded in your application, please refer to yFiles Library Formats.

Importing the ES Modules of yFiles for HTML

You can load yFiles for HTML by using the JS Module variant of the library and import statements:

Importing ES modules with symbolic names
import { GraphComponent } from 'yfiles'
const graphComponent = new GraphComponent()

Importing symbolic modules like in the above snippets requires either working import-maps, or some preprocessing to resolve the symbolic names (e.g. with webpack) for the browser. The yFiles for HTML package uses Vite to serve the demos.

The advantage of using symbolic imports is the seamless integration in modern web application tooling and provides the best IDE support without additional manual configuration.

Nevertheless, you can avoid the necessary preprocessing by importing JS Modules from the actual JavaScript file in browsers that support native JS Module imports:

Importing relative ES modules
import { GraphComponent } from '../../node_modules/yfiles/yfiles.js'
const graphComponent = new GraphComponent()

For more details on the JS Modules, please refer to Importing the ES Modules of yFiles for HTML.

Adding the Main Component

Continue by adding a GraphComponent instance to the top-level container. The GraphComponent is one of the most central classes of yFiles for HTML and used for displaying and editing graphs.

Initialize the GraphComponent using a CSS selector or id to tell it which existing div element to use:

Initialization of GraphComponent, the Main Component
const graphComponent = new GraphComponent('#graphComponent')

The basic yFiles for HTML application is complete now. The source of this application should look like the code below. For our first web application, we make use of JS Module loading.

Basic yFiles for HTML application files
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>yFiles for HTML Sample Web App</title>
    <!-- supply some sane defaults for the graph component and page -->
    <style>html, body, #graphComponent { width: 100%; height: 100%; padding: 0; margin: 0; }</style>
  </head>
  <body>
    <div id="graphComponent"></div>
    <script type="module" crossorigin="anonymous" src="MyFirstApplication.js"></script>
</body>
</html>
import { GraphComponent, License } from 'yfiles'

License.value = {
  /* <add your license information here> */
}

const graphComponent = new GraphComponent('#graphComponent')

Now open the web page in your browser. You should see an empty white canvas, filling the complete display area. It will probably look like an empty web page, however it may display a license notice at the bottom left and if you use the mouse wheel in the empty area you will see that scroll bars will appear.

In anticipation of the tutorial step User Interaction: you can enable graph editing by inserting this line of code: graphComponent.inputMode = new GraphEditorInputMode(). Don’t forget adding GraphEditorInputMode to the import statement!

In the following sections, we’ll insert code snippets into the application step-by-step. At the end of each section, we’ll summarize the source code created so far.

Creating Graph Elements

In this section we will learn how to create graph elements in yFiles for HTML. We’ll create a small sample graph which will be displayed immediately after the application has started. The graph class and its features are discussed thoroughly in the chapter The Graph Model.

The graph is modeled by interface IGraph. Instances of this interface hold the graph model itself but also provide methods to create, change, and remove graph elements. An instance of IGraph can be obtained from the GraphComponent.A graph consists of different types of elements. Nodes and edges are modeled by instances of INode and IEdge, respectively. Further element types are labels (ILabel) which add textual information to nodes and edges. Finally, ports (IPort) serve as connection points of edges to nodes or other edges, and bends (IBend) provide control points for edges.All kinds of graph elements are created with factory methods of IGraph.

A new GraphComponent instance holds already an implementation of the IGraph interface. This instance is the graph which will be displayed by the GraphComponent. You can access it via the graph property.

Insert the following line after the initialization of the GraphComponent:

Getting the graph of a GraphComponent
const graph = graphComponent.graph

Creating Nodes

The only way to create new nodes is using IGraph’s createNode method. Let’s create some nodes at different locations and with different sizes:

Creating nodes
import { Rect } from 'yfiles'
const node1 = graph.createNode(new Rect(0, 0, 30, 30))
const node2 = graph.createNode(new Rect(100, 0, 30, 30))
const node3 = graph.createNode(new Rect(300, 300, 60, 30))

It’s time to reload the browser page.

Hm, neat but it could be better. At least, the graph should be centered in the display. Fortunately, GraphComponent provides a method to fit the graph so it is centered in the display: fitGraphBounds. Call this method after creating all graph elements and after the GUI was created:

Fitting the graph in the visible area
graphComponent.fitGraphBounds()

By default, a node is displayed as white rectangle with a black border. Thus, the diagram with three nodes looks currently like this:

Creating Edges

A graph is defined by a set of nodes and a set of edges which define the relations between the nodes. So, let’s connect the nodes by some edges. To do so, use IGraph’s createEdge method to connect node1 with node2 and node2 with node3:

Creating edges
const edge1 = graph.createEdge(node1, node2)
const edge2 = graph.createEdge(node2, node3)

The first node of an edge is called source node of the edge and the second node is called target node.

Now we have a graph with three nodes and two edges connecting them. An edge is drawn with black, straight line segments by default. The result should look like this:

Using Bends

Right now, the edges are represented by straight lines. yFiles for HTML supports the insertion of bends to draw complex edge paths. Bends are points that subdivide edges into segments. These points are modeled by interface IBend.

Bends are created for a specific edge using IGraph’s addBend(IEdge, Point, number) method. An edge can have an arbitrary number of bends.

The following line adds a bend to the edge edge2, its coordinates are chosen to result in an orthogonal bend at this location.

Adding bends
import { Point } from 'yfiles'
const bend1 = graph.addBend(edge2, new Point(330, 15))

Our diagram looks like this now:

Using Ports

A port is a graph element to which edges can connect. Ports can be added to nodes or edges (although ports on edges are somewhat rare).

In fact, edges always connect to ports, not directly to nodes. Wait — a few lines above we connected two nodes using createEdge(INode, INode, IEdgeStyle, Object), didn’t we? Actually, we didn’t. Rather, the createEdge method implicitly created two ports at the center of the source and target node first, then connected these ports with the edge.

If your use case does not require dedicated port objects, you can simply ignore IPorts and just work with nodes. In this case, yFiles for HTML takes care to create and remove IPorts as needed.

If necessary, you can manually create ports at nodes and let the edges connect to these. IGraph’s addPort method creates a port for the given owner. In this example we use two ways of specifying its arguments:

  • addPort(owner, parameter) creates a port for the given node or edge at the location defined by the port location parameter. Placing ports is described in more detail later in this tutorial in section Placing Ports.
  • addPort(owner) creates a new port for the given owner node or edge with the default port location parameter (i.e. the port is located at the center of the node).

Finally, we create a new edge between the newly created ports:

Adding ports
import { FreeNodePortLocationModel } from 'yfiles'
const portAtNode1 = graph.addPort(node1)
const portAtNode3 = graph.addPort(node3, FreeNodePortLocationModel.NODE_LEFT_ANCHORED)
const edgeAtPorts = graph.createEdge(portAtNode1, portAtNode3)

By default, ports are invisible. However, due to the dedicated port location parameter of port portAtNode3, the new edge points to the middle of the left side of its target node instead of its center:

Adding Labels

Usually, one has to add supplemental information to a graph and its elements. Textual information can be represented by labels, which are implementations of ILabel. Similar to bends and ports, labels are added to nodes, edges, and ports via the graph, using IGraph’s addLabel method, and every label owner can have more than one label.

Now we add some labels to our graph items. Add the following lines to your initialization code and reload the page:

Adding labels
const ln1 = graph.addLabel(node1, 'n1')
const ln2 = graph.addLabel(node2, 'n2')
const ln3 = graph.addLabel(node3, 'n3')
const le3 = graph.addLabel(edgeAtPorts, 'edgeAtPorts')

Similar to ports, labels are typically placed relative to their owner element by specifying a specific label model parameter. This is described later in this tutorial in section Placing Labels.

Removing Elements

Any element can be removed from the graph using its remove(IModelItem) method. Note that when an element is removed all dependent elements will be removed, too.

For example, when a node is removed, all edges which connect to that node will be removed as well. Also, all its labels and ports will be removed, too.

The dependent elements are removed before the element they depend on — that way the graph will always be in a consistent state.

Summary of Creating Elements

With all code snippets of this section, the source code of the example page looks like this:

The example application files after creating graph elements
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>yFiles for HTML Sample Web App</title>
    <!-- supply some sane defaults for the graph component and page -->
    <style>html, body, #graphComponent { width: 100%; height: 100%; padding: 0; margin: 0; }</style>
  </head>
  <body>
    <div id="graphComponent"></div>
    <script type="module" crossorigin="anonymous" src="MyFirstApplication.js"></script>
</body>
</html>
import { FreeNodePortLocationModel, GraphComponent, License, Point, Rect } from 'yfiles'

License.value = {
  /** <add your license information here> */
}

const graphComponent = new GraphComponent('#graphComponent')
const graph = graphComponent.graph

const node1 = graph.createNode(new Rect(0, 0, 30, 30))
const node2 = graph.createNode(new Rect(100, 0, 30, 30))
const node3 = graph.createNode(new Rect(300, 300, 60, 30))

const edge1 = graph.createEdge(node1, node2)
const edge2 = graph.createEdge(node2, node3)

const bend1 = graph.addBend(edge2, new Point(330, 15))

const portAtNode1 = graph.addPort(node1)
const portAtNode3 = graph.addPort(node3, FreeNodePortLocationModel.NODE_LEFT_ANCHORED)
const edgeAtPorts = graph.createEdge(portAtNode1, portAtNode3)

const ln1 = graph.addLabel(node1, 'n1')
const ln2 = graph.addLabel(node2, 'n2')
const ln3 = graph.addLabel(node3, 'n3')
const le3 = graph.addLabel(edgeAtPorts, 'edgeAtPorts')

graphComponent.fitGraphBounds()

User Interaction

In this section we will learn how to enable the default user interaction capabilities. Almost every aspect of user interacting can be configured and customized to fit your needs. This is discussed thoroughly in the chapters User Interaction and Customizing User Interaction.

User interaction is enabled by setting an input mode to the GraphComponent. yFiles for HTML provides a comprehensive input mode which enables the most common tasks for editing graphs, the GraphEditorInputMode.

If you already had a look at the demos that come with yFiles for HTML, you may have noticed that often the user can drag nodes around with the mouse, and can add and remove nodes, edges, and bends. If you tried to click and drag in the example web page, nothing happened. This will change now.

Create a new instance of the GraphEditorInputMode class and set it as the GraphComponent’s input mode using the inputMode property. In the example web page, add the following line after the instantiation of the GraphComponent:

Enabling default user interaction features
import { GraphEditorInputMode } from 'yfiles'
graphComponent.inputMode = new GraphEditorInputMode()

In yFiles for HTML, interaction is handled by input modes. Class GraphEditorInputMode is the main input mode and already configured to handle the most common tasks, such as moving, deleting, creating, and resizing graph elements. Although there are much more input modes, these are mostly tailored for specific tasks and often automatically invoked by GraphEditorInputMode when needed.

Adding GraphEditorInputMode enables the following features and more:

  • Selecting a single element by just clicking it. Hold Alt to step through different elements at the same location, e.g. a node label inside its owner. To select multiple elements, either extend an existing selection by pressing Ctrl while clicking, or drag a selection rectangle over all graph elements that you want in your selection. Ctrl+A selects all elements.
  • Resizing nodes. Drag one of the handles that appear when a node is selected.
  • Moving a node or a bend by dragging it when it is selected.
  • Creating an edge. Start dragging anywhere on an unselected source node and stop dragging on the target node.
  • Creating a bend in an edge. Click and drag the edge at the desired bend location.
  • Creating or editing a label. Press F2 when the label’s owner is selected.
  • Moving a label. Select it and drag it to the desired location. Note that the valid positions are restricted by the label model of a label. These positions show up as empty rectangles when you start dragging the label. You can only move a label to one of these positions.

Setting Styles

In this section we will learn how to change the visual appearance of graph elements. This is discussed thoroughly in the sections Visualization of Graph Elements: Styles and Customizing Styles.

The rendering of graph elements of all kinds except bends is handled by styles. Style implementations are type-specific, for example a node is rendered by an INodeStyle, an edge by an IEdgeStyle.

In a typical diagram, the visualization of the nodes and the other elements must be customized to convey some kind of information or to be more attractive than the monochrome shapes we used so far.

In yFiles for HTML all kinds of graph elements (nodes, edges, labels, ports, but not bends) have a so-called style which is responsible for the rendering of the element. For example, for nodes, the interface INode has a style property with the type INodeStyle.

Different node styles
The default node style
A simple colored rectangle
An image
A complex style used in an organization chart

You don’t have to rely on the predefined styles. yFiles for HTML makes it rather easy to implement your own style that perfectly fits your needs. See chapter Customizing Styles to learn more about this.

The style of a graph element can be set at creation time, and/or changed at any time afterwards with the setStyle methods of IGraph. In any case, graph elements always must have a non-null style.

All creation methods support a style parameter:

For example, to set the style of some nodes to a flat orange rectangle without border we can use the following lines:

Setting a node style
import { ShapeNodeStyle } from 'yfiles'
// create a style which draws a node as a geometric shape with a fill and a transparent border color
const orangeNodeStyle = new ShapeNodeStyle({
  shape: 'rectangle',
  fill: 'orange',
  stroke: 'transparent'
})

// change the style of an already created node
graph.setStyle(node3, orangeNodeStyle)

// set a custom style at node creation
const node4 = graph.createNode(new Rect(200, 80, 60, 30), orangeNodeStyle)

Now, the diagram has two orange nodes:

If the style of an element is not specified explicitly, a default style will be used. The default is not hard-coded in the library. Rather, it can be set as a part of a number of default properties for each kind of graph item. This is discussed in detail in the section Setting Defaults for new Items.

For nodes the default style can be set using the NodeDefaults class in the following way:

Changing the default node style
const blueNodeStyle = new ShapeNodeStyle({
  fill: 'cornflower_blue',
  stroke: 'transparent'
})

graph.nodeDefaults.style = blueNodeStyle

Changing the default style will not affect already created nodes, therefore this line should be inserted before the creation of the nodes.

As a result, we get two blue nodes and two orange nodes:

With the additions for setting the node styles, the source code of the example web page is this:

The example application files after changing node styles
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>yFiles for HTML Sample Web App</title>
    <!-- supply some sane defaults for the graph component and page -->
    <style>html, body, #graphComponent { width: 100%; height: 100%; padding: 0; margin: 0; }</style>
  </head>
  <body>
    <div id="graphComponent"></div>
    <script type="module" crossorigin="anonymous" src="MyFirstApplication.js"></script>
</body>
</html>
import {
  FreeNodePortLocationModel,
  GraphComponent,
  GraphEditorInputMode,
  License,
  Point,
  Rect,
  ShapeNodeStyle
} from 'yfiles'

License.value = {
  /* <add your license information here> */
}

const graphComponent = new GraphComponent('#graphComponent')
const graph = graphComponent.graph

const blueNodeStyle = new ShapeNodeStyle({
  fill: 'cornflower_blue',
  stroke: 'transparent'
})

// newly, interactively created nodes will be blue
graph.nodeDefaults.style = blueNodeStyle

const node1 = graph.createNode(new Rect(0, 0, 30, 30))
const node2 = graph.createNode(new Rect(100, 0, 30, 30))
const node3 = graph.createNode(new Rect(300, 300, 60, 30))

const edge1 = graph.createEdge(node1, node2)
const edge2 = graph.createEdge(node2, node3)

const bend1 = graph.addBend(edge2, new Point(330, 15))

const portAtNode1 = graph.addPort(node1)
const portAtNode3 = graph.addPort(node3, FreeNodePortLocationModel.NODE_LEFT_ANCHORED)
const edgeAtPorts = graph.createEdge(portAtNode1, portAtNode3)

const ln1 = graph.addLabel(node1, 'n1')
const ln2 = graph.addLabel(node2, 'n2')
const ln3 = graph.addLabel(node3, 'n3')
const le3 = graph.addLabel(edgeAtPorts, 'edgeAtPorts')

graphComponent.fitGraphBounds()
graphComponent.inputMode = new GraphEditorInputMode()

// create a style which draws a node as a geometric shape with a fill and a transparent border color
const orangeNodeStyle = new ShapeNodeStyle({
  shape: 'rectangle',
  fill: 'orange',
  stroke: 'transparent'
})

// change the style of an already created node
graph.setStyle(node3, orangeNodeStyle)

// set a custom style at node creation
const node4 = graph.createNode(new Rect(200, 80, 60, 30), orangeNodeStyle)

Placing Graph Elements

In this section we will learn how to explicitly place individual graph elements at certain locations. All details about placing graph elements are provided in section Item Layout.

The outstanding automatic graph layout features of yFiles for HTML are discussed later in section Automatic Graph Layout.

A node’s geometry, its layout, is described by a rectangle which defines the position and size of the node.An edge has no information about its geometry by itself. Its path is defined by the location of its source and target ports and the number and location of its bends.A label’s geometry is described by a so-called label model parameter which usually defines its position relative to the label’s owner, see also section Labels. Similarly, a port’s position is described by a so-called port location model parameter (see section Ports).

Placing Nodes

A node’s size and location — its layout or bounding box — is defined by INode’s layout, which represents a rectangle.

With regards to the node layout, there are three different ways of specifying the node layout when using the createNode method:

IGraph.createNodeAt(point)
Creates a node at the provided location with the default size. Note that the point specifies the location of the center of the node, not the top left corner.
IGraph.createNode()
Creates a node at the default location (0,0) with the default size. This is mainly useful in combination with an automatic layout which calculates better locations for nodes.

To change the location or size of existing nodes, you can use the following methods:

IGraph.setNodeCenter(INode, Point)
Sets the center of the provided node to the provided location.
IGraph.setNodeLayout(INode, Rect)
Sets the location and size of the provided node to the provided rectangle.

Placing Bends

A bend’s location is defined by IBend’s Location property as a point with absolute coordinates.

The bend location can be set

Placing Labels

Usually, the location of a label is not specified by coordinates. Instead, its location is defined relative to its owner element with the help of a label model parameter. Different label model parameters and their usage is discussed thoroughly in section Labels.

Label model parameters can be set

Placing Ports

Similar to labels, ports are also typically placed relative to their owner, and their location is determined by a port location model parameter. Different port location parameters and their usage is discussed thoroughly in section Ports.

Port location model parameters can be set

Automatic Graph Layout

In this section we will learn how to automatically arrange the graph with one of the automatic graph layout styles provided by yFiles for HTML. The chapter Automatic Graph Layout introduces all layout styles of yFiles for HTML and thoroughly explains their configuration options.

yFiles for HTML can automatically arrange your diagram in all major graph layout styles including hierarchical, organic, tree, orthogonal, and circular style. The IGraph and GraphComponent classes provide convenient methods to apply these algorithms to the graph in a single method call.

An important part of diagram creation is the arrangement of its elements in a way that not only presents the elements in a clear and easily readable way but also emphasizes their structural characteristics like clustering or a flow direction. yFiles for HTML provides a wide range of different layout styles, suitable for different application fields.

The most important layout styles are shown below:

Hierarchical
Organic
Tree
Orthogonal

Besides these layout styles yFiles for HTML offers further layout algorithms as well as algorithms to route edges and place labels without altering the node layout.

In this example we use a force-directed layout, implemented by the OrganicLayout class, and alter its default settings slightly:

Initialization of an organic layout
const layout = new OrganicLayout()
layout.considerNodeSizes = true
layout.minimumNodeDistance = 50

The easiest way to automatically layout a diagram is to use one of the following methods:

  • applyLayout works on an IGraph and applies the layout in a blocking call. When the method returns all elements in the graph are already arranged at their final coordinates.
  • morphLayout applies the specified layout algorithm to the GraphComponent’s graph and offers some more features: The new layout is (optionally) applied in an animated fashion, and the final arrangement is centered in the GraphComponent similar to the fitGraphBounds method.

To customize the animation from old to new layout, use the LayoutExecutor class instead of morphLayout. See section Applying an Automatic Layout for details.

Add the following line of code at the end of your file to layout the example diagram once during initialization of the web app. Note that the LayoutExecutor is required for this internally and in order to not let some automatic bundlers omit that functionality from the bundle, we need to ensure that it is there when we want to apply a layout to an IGraph via the applyLayout and morphLayout utility functions.

Calculating and applying an automatic using async/await
// make sure bundlers don't optimize away the layout execution functionality
Class.ensure(LayoutExecutor)

try {
  await graphComponent.morphLayout(layout)
  alert('Done!')
} catch (e) {
  alert(`An error occurred during layout ${e}`)
}
Calculating and applying an automatic layout
// make sure bundlers don't optimize away the layout execution functionality
Class.ensure(LayoutExecutor)

try {
  await graphComponent.morphLayout(layout)
  alert('Done!')
} catch (error) {
  alert(`An error occurred during layout ${error}`)
}

In the next subsection, we create a toolbar button that calculates a layout on demand. Skip to the next section Loading and Saving Graphs if you don’t need this.

Adding a Layout Button

In this section we will provide a simple HTML button interface to the web page which executes the automatic layout.

First, we add a button to the page. Since morphLayout calculates and morphs the layout asynchronously, we disable the button before the calculation starts and re-enable it after the layout (and the subsequent animation) has finished. Invoking code after the layout — that’s where return type Promise of the morphLayout method comes in handy:

An procedure for applying an automatic arrangement
// perform layout on button click
const layoutButton = document.querySelector('#layoutButton')
layoutButton.addEventListener('click', async () => {
  // disable and re-enable the button before and after morphing the layout
  layoutButton.setAttribute('disabled', 'disabled')
  try {
    await graphComponent.morphLayout(layout)
  } catch (e) {
    alert(`An error occurred during layout ${e}`)
  } finally {
    layoutButton.removeAttribute('disabled')
  }
})
// perform layout on button click
const layoutButton = document.querySelector('#layoutButton')!
layoutButton.addEventListener('click', async () => {
  // disable and re-enable the button before and after morphing the layout
  layoutButton.setAttribute('disabled', 'disabled')
  try {
    await graphComponent.morphLayout(layout)
  } catch (e) {
    alert(`An error occurred during layout ${e}`)
  } finally {
    layoutButton.removeAttribute('disabled')
  }
})

Then, we add this code to the button and get the following web page:

The example application files with the layout button
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>yFiles for HTML Sample Web App</title>
    <!-- supply some sane defaults for the graph component and page -->
    <style>html, body, #graphComponent { width: 100%; height: 100%; padding: 0; margin: 0; }</style>
  </head>
  <body>
    <button id="layoutButton">Layout</button>
    <div id="graphComponent"></div>
    <script type="module" crossorigin="anonymous" src="MyFirstApplication.js"></script>
  </body>
</html>
import {
  Class,
  FreeNodePortLocationModel,
  GraphComponent,
  GraphEditorInputMode,
  LayoutExecutor,
  License,
  OrganicLayout,
  Point,
  Rect,
  ShapeNodeStyle
} from 'yfiles'

License.value = {
  /* <add your license information here> */
}

// We need to load the LayoutExecutor from the yfiles/view-layout-bridge module
// which is needed for 'morphLayout' in this demo.
Class.ensure(LayoutExecutor)

const graphComponent = new GraphComponent('#graphComponent')
const graph = graphComponent.graph

const blueNodeStyle = new ShapeNodeStyle({
  fill: 'cornflower_blue',
  stroke: 'transparent'
})

// newly, interactively created nodes will be blue
graph.nodeDefaults.style = blueNodeStyle

const node1 = graph.createNode(new Rect(0, 0, 30, 30))
const node2 = graph.createNode(new Rect(100, 0, 30, 30))
const node3 = graph.createNode(new Rect(300, 300, 60, 30))

const edge1 = graph.createEdge(node1, node2)
const edge2 = graph.createEdge(node2, node3)

const bend1 = graph.addBend(edge2, new Point(330, 15))

const portAtNode1 = graph.addPort(node1)
const portAtNode3 = graph.addPort(node3, FreeNodePortLocationModel.NODE_LEFT_ANCHORED)
const edgeAtPorts = graph.createEdge(portAtNode1, portAtNode3)

const ln1 = graph.addLabel(node1, 'n1')
const ln2 = graph.addLabel(node2, 'n2')
const ln3 = graph.addLabel(node3, 'n3')
const le3 = graph.addLabel(edgeAtPorts, 'edgeAtPorts')

graphComponent.fitGraphBounds()
graphComponent.inputMode = new GraphEditorInputMode()

const layout = new OrganicLayout()
layout.considerNodeSizes = true
layout.minimumNodeDistance = 50

// run layout immediately with Promise handling, not using async/await
graphComponent
  .morphLayout(layout)
  .then(() => alert('Done!'))
  .catch((e) => alert(`An error occurred during layout ${e}`))

// perform layout on button click, using async/await
const layoutButton = document.querySelector('#layoutButton')
layoutButton.addEventListener('click', async () => {
  // disable and re-enable the button before and after morphing the layout
  layoutButton.setAttribute('disabled', 'disabled')
  try {
    await graphComponent.morphLayout(layout)
  } catch (e) {
    alert(`An error occurred during layout ${e}`)
  } finally {
    layoutButton.removeAttribute('disabled')
  }
})
import {
  Class,
  FreeNodePortLocationModel,
  GraphComponent,
  GraphEditorInputMode,
  LayoutExecutor,
  License,
  OrganicLayout,
  Point,
  Rect,
  ShapeNodeStyle
} from 'yfiles'

License.value = {
  /* <add your license information here> */
}

// We need to load the LayoutExecutor from the yfiles/view-layout-bridge module
// which is needed for 'morphLayout' in this demo.
Class.ensure(LayoutExecutor)

const graphComponent = new GraphComponent('#graphComponent')
const graph = graphComponent.graph

const blueNodeStyle = new ShapeNodeStyle({
  fill: 'cornflower_blue',
  stroke: 'transparent'
})

// newly, interactively created nodes will be blue
graph.nodeDefaults.style = blueNodeStyle

const node1 = graph.createNode(new Rect(0, 0, 30, 30))
const node2 = graph.createNode(new Rect(100, 0, 30, 30))
const node3 = graph.createNode(new Rect(300, 300, 60, 30))

const edge1 = graph.createEdge(node1, node2)
const edge2 = graph.createEdge(node2, node3)

const bend1 = graph.addBend(edge2, new Point(330, 15))

const portAtNode1 = graph.addPort(node1)
const portAtNode3 = graph.addPort(node3, FreeNodePortLocationModel.NODE_LEFT_ANCHORED)
const edgeAtPorts = graph.createEdge(portAtNode1, portAtNode3)

const ln1 = graph.addLabel(node1, 'n1')
const ln2 = graph.addLabel(node2, 'n2')
const ln3 = graph.addLabel(node3, 'n3')
const le3 = graph.addLabel(edgeAtPorts, 'edgeAtPorts')

graphComponent.fitGraphBounds()
graphComponent.inputMode = new GraphEditorInputMode()

const layout = new OrganicLayout()
layout.considerNodeSizes = true
layout.minimumNodeDistance = 50

// run layout immediately with Promise handling, not using async/await
graphComponent
  .morphLayout(layout)
  .then(() => alert('Done!'))
  .catch((e) => alert(`An error occurred during layout ${e}`))

// perform layout on button click, using async/await
const layoutButton = document.querySelector('#layoutButton')!
layoutButton.addEventListener('click', async () => {
  // disable and re-enable the button before and after morphing the layout
  layoutButton.setAttribute('disabled', 'disabled')
  try {
    await graphComponent.morphLayout(layout)
  } catch (e) {
    alert(`An error occurred during layout ${e}`)
  } finally {
    layoutButton.removeAttribute('disabled')
  }
})

Loading and Saving Graphs

In this section we will learn how to load and save graphs. Details of loading and saving as well as image export and printing are discussed thoroughly in the chapter Graph I/O and Printing.

yFiles for HTML has built-in support for saving and loading diagrams in the GraphML format. GraphML is an XML-based format that stores all aspects of a diagram, especially the graph elements, their appearance, and optionally any custom data.

Before you can interactively save and load the diagram shown in a GraphComponent, these file operations must be enabled:

Enabling loading and saving
import { GraphMLSupport } from 'yfiles'
const support = new GraphMLSupport(graphComponent)
support.storageLocation = StorageLocation.FILE_SYSTEM

Once file operations have been enabled on the graph control, the following commands can be used:

File operation commands
Command Description Keyboard Shortcut
SAVEPossibly opens a file dialog that lets the user choose where to save the graph.Ctrl+S
OPENShows an open file dialog and loads the chosen file into the GraphComponent.Ctrl+O

Commands are discussed thoroughly in chapter Commands. For now, it should be sufficient to know that you can easily simply execute a command:

Buttons for loading and saving
import { ICommand } from 'yfiles'
// execute open and save commands on button click
document.querySelector('#openButton').addEventListener('click', () => {
  ICommand.OPEN.execute(null, graphComponent)
})
document.querySelector('#saveButton').addEventListener('click', () => {
  ICommand.SAVE.execute(null, graphComponent)
})
import { ICommand } from 'yfiles'
// execute open and save commands on button click
document.querySelector('#openButton')!.addEventListener('click', () => {
  ICommand.OPEN.execute(null, graphComponent)
})
document.querySelector('#saveButton')!.addEventListener('click', () => {
  ICommand.SAVE.execute(null, graphComponent)
})

With these two code snippets, we get the final example web page:

The final example application files
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>yFiles for HTML Sample Web App</title>
    <!-- supply some sane defaults for the graph component and page -->
    <style>html, body, #graphComponent { width: 100%; height: 100%; padding: 0; margin: 0; }</style>
  </head>
  <body>
    <button id="openButton">Open</button>
    <button id="saveButton">Save</button>
    <button id="layoutButton">Layout</button>
    <div id="graphComponent"></div>
    <script type="module" crossorigin="anonymous" src="MyFirstApplication.js"></script>
  </body>
</html>
import {
  Class,
  FreeNodePortLocationModel,
  GraphComponent,
  GraphEditorInputMode,
  LayoutExecutor,
  License,
  OrganicLayout,
  Point,
  Rect,
  ShapeNodeStyle,
  GraphMLSupport,
  ICommand,
  StorageLocation
} from 'yfiles'

License.value = {
  /* <add your license information here> */
}

// We need to load the LayoutExecutor from the yfiles/view-layout-bridge module
// which is needed for 'morphLayout' in this demo.
Class.ensure(LayoutExecutor)

const graphComponent = new GraphComponent('#graphComponent')
const graph = graphComponent.graph

const blueNodeStyle = new ShapeNodeStyle({
  fill: 'cornflower_blue',
  stroke: 'transparent'
})

// newly, interactively created nodes will be blue
graph.nodeDefaults.style = blueNodeStyle

const node1 = graph.createNode(new Rect(0, 0, 30, 30))
const node2 = graph.createNode(new Rect(100, 0, 30, 30))
const node3 = graph.createNode(new Rect(300, 300, 60, 30))

const edge1 = graph.createEdge(node1, node2)
const edge2 = graph.createEdge(node2, node3)

const bend1 = graph.addBend(edge2, new Point(330, 15))

const portAtNode1 = graph.addPort(node1)
const portAtNode3 = graph.addPort(node3, FreeNodePortLocationModel.NODE_LEFT_ANCHORED)
const edgeAtPorts = graph.createEdge(portAtNode1, portAtNode3)

const ln1 = graph.addLabel(node1, 'n1')
const ln2 = graph.addLabel(node2, 'n2')
const ln3 = graph.addLabel(node3, 'n3')
const le3 = graph.addLabel(edgeAtPorts, 'edgeAtPorts')

graphComponent.fitGraphBounds()
graphComponent.inputMode = new GraphEditorInputMode()

const layout = new OrganicLayout()
layout.considerNodeSizes = true
layout.minimumNodeDistance = 50

// run layout immediately
graphComponent
  .morphLayout(layout)
  .then(() => alert('Done!'))
  .catch((e) => alert('An error occurred during layout ' + e))

// perform layout on button click
const layoutButton = document.querySelector('#layoutButton')
layoutButton.addEventListener('click', async () => {
  // disable and re-enable the button before and after morphing the layout
  layoutButton.setAttribute('disabled', 'disabled')
  try {
    await graphComponent.morphLayout(layout)
  } catch (e) {
    alert(`An error occurred during layout ${e}`)
  } finally {
    layoutButton.removeAttribute('disabled')
  }
})

// get a helper class that deals with the UI for loading and saving
const support = new GraphMLSupport(graphComponent)
support.storageLocation = StorageLocation.FILE_SYSTEM

// and bind the commands to buttons
document.querySelector('#openButton').addEventListener('click', () => {
  ICommand.OPEN.execute(null, graphComponent)
})
document.querySelector('#saveButton').addEventListener('click', () => {
  ICommand.SAVE.execute(null, graphComponent)
})

import {
  Class,
  FreeNodePortLocationModel,
  GraphComponent,
  GraphEditorInputMode,
  LayoutExecutor,
  License,
  OrganicLayout,
  Point,
  Rect,
  ShapeNodeStyle,
  GraphMLSupport,
  ICommand,
  StorageLocation
} from 'yfiles'

License.value = {
  /* <add your license information here> */
}

// We need to load the LayoutExecutor from the yfiles/view-layout-bridge module
// which is needed for 'morphLayout' in this demo.
Class.ensure(LayoutExecutor)

const graphComponent = new GraphComponent('#graphComponent')
const graph = graphComponent.graph

const blueNodeStyle = new ShapeNodeStyle({
  fill: 'cornflower_blue',
  stroke: 'transparent'
})

// newly, interactively created nodes will be blue
graph.nodeDefaults.style = blueNodeStyle

const node1 = graph.createNode(new Rect(0, 0, 30, 30))
const node2 = graph.createNode(new Rect(100, 0, 30, 30))
const node3 = graph.createNode(new Rect(300, 300, 60, 30))

const edge1 = graph.createEdge(node1, node2)
const edge2 = graph.createEdge(node2, node3)

const bend1 = graph.addBend(edge2, new Point(330, 15))

const portAtNode1 = graph.addPort(node1)
const portAtNode3 = graph.addPort(node3, FreeNodePortLocationModel.NODE_LEFT_ANCHORED)
const edgeAtPorts = graph.createEdge(portAtNode1, portAtNode3)

const ln1 = graph.addLabel(node1, 'n1')
const ln2 = graph.addLabel(node2, 'n2')
const ln3 = graph.addLabel(node3, 'n3')
const le3 = graph.addLabel(edgeAtPorts, 'edgeAtPorts')

graphComponent.fitGraphBounds()
graphComponent.inputMode = new GraphEditorInputMode()

const layout = new OrganicLayout()
layout.considerNodeSizes = true
layout.minimumNodeDistance = 50

// run layout immediately
graphComponent
  .morphLayout(layout)
  .then(() => alert('Done!'))
  .catch((e) => alert('An error occurred during layout ' + e))

// perform layout on button click
const layoutButton = document.querySelector('#layoutButton')!
layoutButton.addEventListener('click', async () => {
  // disable and re-enable the button before and after morphing the layout
  layoutButton.setAttribute('disabled', 'disabled')
  try {
    await graphComponent.morphLayout(layout)
  } catch (e) {
    alert(`An error occurred during layout ${e}`)
  } finally {
    layoutButton.removeAttribute('disabled')
  }
})

// get a helper class that deals with the UI for loading and saving
const support = new GraphMLSupport(graphComponent)
support.storageLocation = StorageLocation.FILE_SYSTEM

// and bind the commands to buttons
document.querySelector('#openButton')!.addEventListener('click', () => {
  ICommand.OPEN.execute(null, graphComponent)
})
document.querySelector('#saveButton')!.addEventListener('click', () => {
  ICommand.SAVE.execute(null, graphComponent)
})

Next steps

In this short tutorial we learned about the most important concepts of yFiles for HTML and how to use them in a simple web application. Most of the remainder of this development guide describes the various features extensively.

To gain a more in-depth knowledge of yFiles for HTML in an explorative manner, please have a look at the various demos and tutorials contained in the package.