Creating a simple Web Application | Getting Started (2024)

Getting StartedSetting up Your Development Environment

In this section we will create a very basic yFiles for HTML web application, a single page that containsa 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 thepackage. 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 andVisual Studio Code.

Creating the GUI

Before we begin, we recommend creating a folder inside the demos folder where you can put all the filesthat 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 sizevia a CSS rule.

The GraphComponent does not have a minimum size configured by default. It will always use the sizethat 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 otherwiseyou 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 filesinto the browser. Then, we create a separate javascript file (MyFirstApplication.js) which will beloaded via a <script> tag from our index.html page.

<!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 sectionon 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 thelicense 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 supportclasses (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:

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 thesymbolic 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 toolingand provides the best IDE support without additional manual configuration.

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

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. TheGraphComponent 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:

const graphComponent = new GraphComponent('#graphComponent')

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

<!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 ifyou 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 GraphEditorInputModeto 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 samplegraph 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 graphmodel itself but also provide methods to create, change, and remove graph elements. An instance of IGraphcan be obtained from the GraphComponent.A graph consists of different types of elements. Nodes and edges are modeled by instances of INode andIEdge, respectively. Further element types are labels (ILabel) which add textualinformation to nodes and edges. Finally, ports (IPort) serve as connection pointsof 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:

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:

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, GraphComponentprovides 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:

graphComponent.fitGraphBounds()

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

Creating a simple Web Application | Getting Started (1)

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’screateEdge method toconnect node1 with node2 and node2 with node3:

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 calledtarget 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:

Creating a simple Web Application | Getting Started (2)

Using Bends

Right now, the edges are represented by straight lines. yFiles for HTML supports the insertionof 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’saddBend(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 orthogonalbend at this location.

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

Our diagram looks like this now:

Creating a simple Web Application | Getting Started (3)

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 aresomewhat rare).

In fact, edges always connect to ports, not directly to nodes. Wait — a few lines above we connected twonodes using createEdge(INode, INode, IEdgeStyle, Object), didn’t we?Actually, we didn’t. Rather, the createEdge method implicitlycreated 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 workwith 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’saddPort 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 thegiven 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:

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:

Creating a simple Web Application | Getting Started (4)

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’saddLabel 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:

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 aspecific label model parameter. This is described later in this tutorial in sectionPlacing Labels.

Creating a simple Web Application | Getting Started (5)

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 aconsistent state.

Summary of Creating Elements

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

<!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.graphconst 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 forediting 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 aroundwith 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 asthe GraphComponent’s input mode using the inputMode property.In the example web page, add the following line after the instantiation of the GraphComponent:

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 tohandle 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 oftenautomatically 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 throughdifferent 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 restrictedby 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 implementationsare type-specific, for example a node is rendered by an INodeStyle, an edge by anIEdgeStyle.

In a typical diagram, the visualization of the nodes and the other elements must be customized toconvey 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 stylewhich is responsible for the rendering of the element. For example, for nodes, the interface INode has astyle property with the type INodeStyle.

Creating a simple Web Application | Getting Started (6)
Creating a simple Web Application | Getting Started (7)
Creating a simple Web Application | Getting Started (8)
Creating a simple Web Application | Getting Started (9)

You don’t have to rely on the predefined styles. yFiles for HTML makes it rather easy to implementyour 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 withthe setStyle methods of IGraph. In any case, graph elementsalways must have a non-null style.

All creation methods support a style parameter:

  • createNode(layout:Rect, style:INodeStyle, tag:Object):INode
  • createEdge(source:INode, target:INode, style:IEdgeStyle, tag:Object):IEdge
  • addLabel(owner:ILabelOwner, text:string, layoutParameter:ILabelModelParameter, style:ILabelStyle, preferredSize:Size, tag:Object):ILabel
  • addPort(owner:IPortOwner, locationParameter:IPortLocationModelParameter, style:IPortStyle, tag:Object):IPort

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

import { ShapeNodeStyle } from 'yfiles'// create a style which draws a node as a geometric shape with a fill and a transparent border colorconst orangeNodeStyle = new ShapeNodeStyle({ shape: 'rectangle', fill: 'orange', stroke: 'transparent'})// change the style of an already created nodegraph.setStyle(node3, orangeNodeStyle)// set a custom style at node creationconst node4 = graph.createNode(new Rect(200, 80, 60, 30), orangeNodeStyle)

Now, the diagram has two orange nodes:

Creating a simple Web Application | Getting Started (10)

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 classin the following way:

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 beinserted before the creation of the nodes.

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

Creating a simple Web Application | Getting Started (11)

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

<!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.graphconst blueNodeStyle = new ShapeNodeStyle({ fill: 'cornflower_blue', stroke: 'transparent'})// newly, interactively created nodes will be bluegraph.nodeDefaults.style = blueNodeStyleconst 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 colorconst orangeNodeStyle = new ShapeNodeStyle({ shape: 'rectangle', fill: 'orange', stroke: 'transparent'})// change the style of an already created nodegraph.setStyle(node3, orangeNodeStyle)// set a custom style at node creationconst 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 sectionAutomatic 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 itssource 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 positionrelative to the label’s owner, see also section Labels. Similarly, a port’s positionis 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’slayout, which represents a rectangle.

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

IGraph.createNodeAt(point)
Creates a node at the provided location with thedefault size. Note that the point specifiesthe location of the center of the node, not the top left corner.
IGraph.createNode()
Creates a node at thedefault location (0,0) with the default size. This is mainly usefulin 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

  • for a new bend by providing its initial absolute coordinates toIGraph.addBend(edge:IEdge, location:Point, index:number):IBend
  • for an existing bend by providing its new absolute coordinates toIGraph.setBendLocation(bend:IBend, location:Point):void

Placing Labels

Usually, the location of a label is not specified by coordinates. Instead, its location is definedrelative 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

  • for a new label by providing a label model parameter toIGraph.addLabel(ILabelOwner, string, ILabelModelParameter, ILabelStyle, Size, Object).
  • for an existing label by providing a label model parameter toIGraph.setLabelLayoutParameter(ILabel, ILabelModelParameter).
  • by changing the defaults. Note that the defaults for labels, modeled by ILabelDefaults, are notprovided as property of IGraph but as property of the node defaults and edge defaults.

Placing Ports

Similar to labels, ports are also typically placed relative to their owner, and their location is determinedby 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

  • for a new node by providing a port location model parameter toIGraph.addPort(IPortOwner, IPortLocationModelParameter, IPortStyle, Object).
  • for an existing port by providing a port location model parameter toIGraph.setPortLocationParameter(IPort, IPortLocationModelParameter).
  • by changing the defaults. Note that the defaults for ports, modeled by IPortDefaults, are notprovided as property of IGraph but as property of the node defaults and edge defaults.

Automatic Graph Layout

In this section we will learn how to automatically arrange the graph with one of the automatic graph layoutstyles 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 GraphComponentclasses 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 theelements in a clear and easily readable way but also emphasizes their structural characteristics like clustering or aflow direction. yFiles for HTML provides a wide range of different layout styles, suitable for differentapplication fields.

The most important layout styles are shown below:

Creating a simple Web Application | Getting Started (12)
Creating a simple Web Application | Getting Started (13)
Creating a simple Web Application | Getting Started (14)
Creating a simple Web Application | Getting Started (15)

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

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

const layout = new OrganicLayout()layout.considerNodeSizes = truelayout.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 thegraph are already arranged at their final coordinates.
  • morphLayout applies the specified layout algorithm to the GraphComponent’s graph and offers some morefeatures: The new layout is(optionally) applied in an animated fashion,and the final arrangement is centered in the GraphComponent similar to thefitGraphBounds method.

To customize the animation from old to new layout, use the LayoutExecutor classinstead of morphLayout. See section Applying an Automatic Layoutfor details.

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

// make sure bundlers don't optimize away the layout execution functionalityClass.ensure(LayoutExecutor)try { await graphComponent.morphLayout(layout) alert('Done!')} catch (e) { alert(`An error occurred during layout ${e}`)}
// make sure bundlers don't optimize away the layout execution functionalityClass.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 thenext 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 theautomatic 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 subsequentanimation) has finished. Invoking code after the layout — that’s where return type Promise of themorphLayout method comes in handy:

// perform layout on button clickconst 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 clickconst 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:

<!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.graphconst blueNodeStyle = new ShapeNodeStyle({ fill: 'cornflower_blue', stroke: 'transparent'})// newly, interactively created nodes will be bluegraph.nodeDefaults.style = blueNodeStyleconst 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 = truelayout.minimumNodeDistance = 50// run layout immediately with Promise handling, not using async/awaitgraphComponent .morphLayout(layout) .then(() => alert('Done!')) .catch((e) => alert(`An error occurred during layout ${e}`))// perform layout on button click, using async/awaitconst 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.graphconst blueNodeStyle = new ShapeNodeStyle({ fill: 'cornflower_blue', stroke: 'transparent'})// newly, interactively created nodes will be bluegraph.nodeDefaults.style = blueNodeStyleconst 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 = truelayout.minimumNodeDistance = 50// run layout immediately with Promise handling, not using async/awaitgraphComponent .morphLayout(layout) .then(() => alert('Done!')) .catch((e) => alert(`An error occurred during layout ${e}`))// perform layout on button click, using async/awaitconst 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 operationsmust be enabled:

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
CommandDescriptionKeyboard 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 besufficient to know that you can easily simply execute a command:

import { ICommand } from 'yfiles'// execute open and save commands on button clickdocument.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 clickdocument.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:

<!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.graphconst blueNodeStyle = new ShapeNodeStyle({ fill: 'cornflower_blue', stroke: 'transparent'})// newly, interactively created nodes will be bluegraph.nodeDefaults.style = blueNodeStyleconst 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 = truelayout.minimumNodeDistance = 50// run layout immediatelygraphComponent .morphLayout(layout) .then(() => alert('Done!')) .catch((e) => alert('An error occurred during layout ' + e))// perform layout on button clickconst 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 savingconst support = new GraphMLSupport(graphComponent)support.storageLocation = StorageLocation.FILE_SYSTEM// and bind the commands to buttonsdocument.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.graphconst blueNodeStyle = new ShapeNodeStyle({ fill: 'cornflower_blue', stroke: 'transparent'})// newly, interactively created nodes will be bluegraph.nodeDefaults.style = blueNodeStyleconst 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 = truelayout.minimumNodeDistance = 50// run layout immediatelygraphComponent .morphLayout(layout) .then(() => alert('Done!')) .catch((e) => alert('An error occurred during layout ' + e))// perform layout on button clickconst 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 savingconst support = new GraphMLSupport(graphComponent)support.storageLocation = StorageLocation.FILE_SYSTEM// and bind the commands to buttonsdocument.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 HTMLand how to use them in a simple web application. Most of the remainder of thisdevelopment guide describes the various features extensively.

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

Getting StartedSetting up Your Development Environment
Creating a simple Web Application | Getting Started (2024)

References

Top Articles
Latest Posts
Article information

Author: Virgilio Hermann JD

Last Updated:

Views: 5921

Rating: 4 / 5 (61 voted)

Reviews: 92% of readers found this page helpful

Author information

Name: Virgilio Hermann JD

Birthday: 1997-12-21

Address: 6946 Schoen Cove, Sipesshire, MO 55944

Phone: +3763365785260

Job: Accounting Engineer

Hobby: Web surfing, Rafting, Dowsing, Stand-up comedy, Ghost hunting, Swimming, Amateur radio

Introduction: My name is Virgilio Hermann JD, I am a fine, gifted, beautiful, encouraging, kind, talented, zealous person who loves writing and wants to share my knowledge and understanding with you.