DEV Community

Cover image for Create 2.5D Topology Diagrams with Hightopo JavaScript Library
Lalo Salamanca
Lalo Salamanca

Posted on

Create 2.5D Topology Diagrams with Hightopo JavaScript Library

Topology is very important in computer network design and communications as it describes how devices (the "nodes") in a network are interconnected (via "lines"). This structure encompasses both the physical arrangement of components, referred to as physical topology, as well as the logical or virtual connectivity patterns, known as logical topology.

Image description

In this article, we will demonstrate how to create a dynamic topology network with Hightopo's graphics engine "HT for Web" (HT for short).

Creating 2D Views and Connections

In Hightopo, ht.Node is a logical node that can serve as a "point" in the topology diagram. ht.Node supports displaying image icons, which allows for intuitive representation of each "point's" characteristics when creating topology diagrams. As for the "lines" in the topology diagram, this role can be fulfilled by the ht.Edge type. ht.Edge is used to connect source and target nodes, with multiple ht.Edge instances possible between two nodes, and it also allows the source and target to be the same node.

First, before building the topology diagram, we need to create an Hightopo 2D view:

const dm = new ht.DataModel(); // Create a data model
const g2d = new ht.graph.GraphView(dm); // create a 2d view
g2d.addToDOM(); // Add the 2d view to DOM
dm.setBackground('rgb(240,237,237)'); // Set the background color
Enter fullscreen mode Exit fullscreen mode

The 2D view can also enable hierarchical rendering as needed:

dm.setHierarchicalRendering(true);
Enter fullscreen mode Exit fullscreen mode

After creating the 2D view, we can proceed to create "nodes" and "edges":

const node1 = createNode('symbols/电信/icon_交换机.json', { x: 0, y: 0 }, "switcher");
const node2 = createNode('symbols/电信/icon_路由.json', { x: 300, y: 0 }, "router");
createEdge(node1, node2);
function createNode(icon, position, name) {
     const node = new ht.Node(); // create one ht.Node
     node.s({
         'label': name,
         'label.color': "#fff"
     });
     node.setImage(icon);
     node.p(position);
     node.setSize({ width: 100, height: 100 });
     dm.add(node); // add the node to data model
     return node;
}
function createEdge(source, target, color, reverse) {
     const edge = new ht.Edge(source, target); // craete a ht.Edge
     dm.add(edge); // add to data model
     return edge;
}
Enter fullscreen mode Exit fullscreen mode

Here is the result after running the code:

Image description

Complex Connections

The previous section demonstrated a simple example that showed how to use Hightopo to create nodes and connect them with lines. In the real-world, scenarios are often more complex, requiring topology structures to be built based on actual data. Throughout this process, the core steps remain the same: first creating ht.Node instances to represent each endpoint, then using ht.Edge instances to implement connections between nodes. Let's proceed with more complex examples to demonstrate this.

Create Nodes

To create nodes in bulk and manage node data efficiently, the example defines a structured data format stored in a JSON file, which is then retrieved using ht.Default.xhrLoad(). Once the data is obtained, nodes can be created in bulk.

In real-world application scenarios, this data can also be retrieved through any web-based communication methods such as AJAX, fetch or WebSocket.

The data format defined in the equipment.json file:

[
    {
        "name": "coreSwitch1", // Device Name
        "code": "EQ_ASBB1425", // Device ID (Unique ID)
        "icon": "symbols/user/900-word/电信拓扑图标/icon_核心交换机.json", // Device Image Path
        "size": 60, // Node Size
        "position": {
            "x": 0,
            "y": 100
        }
    },
    {
        "name": "coreSwitch2",
        "code": "EQ_ASBB1478",
        "icon": "symbols/user/900-word/电信拓扑图标/icon_核心交换机.json",
        "position": {
            "x": 200,
            "y": 0
        }
    },
    {
        "name": "Server1",
        "code": "EQ_BCGJ2121",
        "icon": "symbols/user/900-word/电信拓扑图标/空白服务器.json",
        "position": {
            "x": 200,
            "y": 250
        }
    },
   ...
]
Enter fullscreen mode Exit fullscreen mode

Get the data and creating nodes in bulk:

ht.Default.xhrLoad('./equipment.json', function (json) {
       const data = ht.Default.parse(json);
       data.forEach((item) => {
             createNode(item);
        })
})
function createNode(data) {
        const node = new ht.Node();
        node.setTag(data.code); // Set a unique identifier for a node
        node.setImage(data.icon);
        node.p(data.position);
        node.s('2d.movable', false); // Turn off movable
        node.setSize({ width: data.size || 150, height: data.size || 150 });
        dm.add(node);
         return node;
}
Enter fullscreen mode Exit fullscreen mode

Image description

Create Connections

Similar to the node data, the example below defines a format for connections, which is also stored in a JSON file and retrieved using ht.Default.xhrLoad(). The JSON file defines the most important elements of a connection: source node, target node, and connection color.

[
    {
        "source": "EQ_ASBB1425", // Unique ID of the start node
        "target": "EQ_BCGJ2121", // Unique ID of the end node
        "color": "rgb(0,199,7)" // Edge color
    },
    {
        "source": "EQ_ASBB1425",
        "target": "EQ_BCGJ2131",
        "color": "rgb(0,199,7)"
},
...
]
Enter fullscreen mode Exit fullscreen mode

Get the edge data and create connections in bulk, this step needs to be executed after creating the nodes:

ht.Default.xhrLoad('./connectData.json', function (json) {
      const connectData = ht.Default.parse(json);
      connectData.forEach((item) => {
            createEdge(item);
      })
})
function createEdge(data) {
    const source = dm.getDataByTag(data.source);
    const target = dm.getDataByTag(data.target);
    const edge = new ht.Edge(source, target);
    edge.s({
        "edge.color": data.color || "rgb(0,199,7)",
        "edge.width": 4,
        "shadow2.offset.x": -4,
        "shadow2.offset.y": 7,
        "shadow2": true,
        "shadow2.color": "rgba(0,0,0,0.18)",
})
dm.add(edge);
    dm.moveToTop(edge); // Move the node to top
    return edge;
}
Enter fullscreen mode Exit fullscreen mode

Image description

At this point, the overall topology effect has been demonstrated, but there may still be some issues. For example, when connections between terminal routers are blocked by servers, it might appear as if there's a connection from Router 1—Server 1—Server 2—Router 2. However, in reality, this connection runs directly from Router 1 to Router 2.

Image description

In such cases, we can use other connection types. ht.Edge provides multiple connection types that can be set using edge.s('edge.type', type). Below are several different connection methods:

  1. Flex:edge.s(‘edge.type’, ‘flex2’)

Image description

  1. Ortho:edge.s(‘edge.type’, ‘ortho2’)

Image description

  1. First horizontal and then vertical:edge.s(‘edge.type’, ‘h.v2’)

Image description

  1. First vertical and then horizontal:edge.s(‘edge.type’, ‘v.h2’)

Image description

......
ht.Edge offers many other types of connection types.

Use Points

In this example, since the connections between two routers need to cross multiple other connections, we use another more flexible approach to make the connections more visually appealing and easier to understand: custom points. This method allows us to freely add control points along the connection path, enabling highly diverse effects.

The points-type connection has two very important properties:

- edge.points: control point information;
- edge.segments: used to specify how the vertex information in the points array should be used during rendering.

Let's change the connection type in this example to points and set the corresponding properties:

edge.s({
    'edge.type': 'points',
    'edge.center': true,
    'edge.points': [
        { "x": 680, "y": 105 },
        { "x": 490, "y": 200 },
        { "x": 470, "y": 200 },
        { "x": 410, "y": 230 },
        { "x": 400, "y": 250 },
        { "x": 360, "y": 270 },
        { "x": 340, "y": 270 },
        { "x": 260, "y": 310 },
        { "x": 250, "y": 330 },
        { "x": 80, "y": 415 }
    ],
    'edge.segments': [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
});
Enter fullscreen mode Exit fullscreen mode

The effect implemented using the points type above:

Image description

Adding Background and Texts

Let’s create ht.Shape nodes as background decorations to highlight specific device areas. ht.Shape can be used to draw different shapes on a 2D View;

createShape([
    { x: -100, y: 130 },
    { x: 230, y: -50 },
    { x: 340, y: 15 },
    { x: 10, y: 195 },
    { x: -100, y: 130 }
], [1, 2, 2, 2, 5])
createShape([
    { x: -155, y: 354 },
    { x: 575, y: -30 },
    { x: 805, y: 110 },
    { x: 60, y: 510 },
    { x: -155, y: 355 },
], [1, 2, 2, 2, 5]);
createShape([
    { x: 300, y: 470 },
    { x: 660, y: 275 },
    { x: 805, y: 350 },
    { x: 435, y: 550 },
    { x: 300, y: 470 },
], [1, 2, 2, 2, 5])
function createShape(points, segments) {
    const shape = new ht.Shape();
    shape.setPoints(points);
    shape.setSegments(segments);
    shape.s({
        'shape.background': "#fff",
        "shape.border.color": "rgba(13,46,79,0.67)",
        "shape.border.width": 0.5,
    })
    dm.add(shape);
    dm.moveToTop(shape);
    return shape;
}
Enter fullscreen mode Exit fullscreen mode

After adding the background:

Image description

Add some text labels - these are essentially also ht.Node, just displaying different icons/images, as shown below:

Image description

Adding Arrows

In complex network topologies, connections often need to indicate the direction of data flow. When using Hightopo to draw connections, ht.Edge provides the icons property, through which you can define a series of icons on ht.Edge and set their positions along the connection.

Before setting the icons property, you need to prepare the icons:

ht.Default.setImage('toArrow', {
    width: 40,
    height: 20,
    comps: [
        {
            type: 'shape',
points: [5, 2, 10, 10, 5, 18, 20, 10],
            closePath: true,
            background: 'rgb(0,199,7)',
            borderWidth: 1,
            borderColor: 'rgb(0,199,7)',
            gradient: 'spread.vertical'
        }
    ]
});
ht.Default.setImage('fromArrow', {
    width: 12,
    height: 12,
    comps: [
        {
            type: 'circle',
            rect: [1, 1, 10, 10],
            background: 'rgb(0,199,7)'
        }
    ]
});
Enter fullscreen mode Exit fullscreen mode

Set icons on ht.Edge:

edge.addStyleIcon("fromArrow", {
     position: 15, // icon position
     keepOrien: true, // Whether to automatically adjust the icon orientation to maintain the best display
     names: ['fromArrow']
});
edge.addStyleIcon("toArrow", {
     position: 19,
     keepOrien: true,
     names: ['toArrow']
});
Enter fullscreen mode Exit fullscreen mode

After adding the arrow:

Image description

Flow Animation

In HT for Web, the ht-flow.js plugin can be used to add flow animation effects to ht.Edge connections. This effect can be used to represent data transmission, energy flow, or any type of dynamic connection. The flow effects implemented using the ht-flow.js plugin are also very simple to configure - after correctly importing the ht-flow.js plugin, use g2d.enableFlow(60); to enable the flow, then set the corresponding flow properties on ht.Edge.

Here are some properties for configuring flow effects using ht.Edge:
_- flow: Boolean value, set to true to enable flow effect.

  • flow.count: Controls the number of flow groups, default is 1.
  • flow.step: Controls the flow step increment, default is 3.
  • flow.element.count: Number of elements in each flow group, default is 10.
  • flow.element.space: Spacing between elements in flow groups, default is 3.5.
  • flow.element.image: String type, specifies the image for elements in flow groups, images must be pre-registered via ht.Default.setImage. Currently supports configuration.
  • flow.element.background: Background color of elements in flow groups, default is rgba(255, 255, 114, 0.4).
  • flow.element.shadow.begincolor: String type, specifies the center color of the gradient shadow for elements in flow groups, default is rgba(255, 255, 0, 0.3).
  • flow.element.shadow.endcolor: String type, specifies the edge color of the gradient shadow for elements in flow groups, default is rgba(255, 255, 0, 0).
  • flow.element.shadow.visible: Whether the flow shadow is visible.
  • flow.begin.percent: Starting position, value from 0 - 1, default is 0.
  • flow.element.autorotate: Whether to auto-rotate, automatically orients based on the angle of the connection._

Set flow properties on the example ht.Edge:

edge.s({
        "flow": true,
        "flow.element.background": "rgba(240, 225, 19, 0.5)",
        "flow.element.shadow.begincolor": "rgba(240, 225, 19, 0.5)",
        "flow.element.shadow.endcolor": "rgba(240, 225, 19, 0)",
        "flow.element.count": 1
 });
Enter fullscreen mode Exit fullscreen mode

The effect after configuration:

Image description

In more complex scenarios, relying solely on simple style configurations cannot meet design requirements. For this reason, ht-flow.js provides the flow.element.image property, which supports setting flowing elements as images or icons, and also supports effects with multiple images/icons flowing.

To set icons on the flow, you need to first register the icons:


ht.Default.setImage('dataIcon1', {
    "width": 50,
    "height": 50,
    "comps": [
        {
            "type": "shape",
            "background": "rgb(125,195,125)",
            "borderColor": "#979797",
            "points": [
                2.94441,
                16.1039,
                26.41008,
                16.1039,
                26.41008,
                4.28571,
                47.05559,
                25.58442,
                27.23783,
                45.71429,
                27.23783,
                33.84863,
                2.94441,
                33.84863,
                2.94441,
                16.1039
            ]
        }
    ]
})
ht.Default.setImage('dataIcon2', {
    "width": 50,
    "height": 50,
    "comps": [
        {
            "type": "shape",
            "background": "#32D3EB",
            "borderColor": "#979797",
            "points": [
                2.94441,
                16.1039,
                26.41008,
                16.1039,
                26.41008,
                4.28571,
                47.05559,
                25.58442,
                27.23783,
                45.71429,
                27.23783,
                33.84863,
                2.94441,
                33.84863,
                2.94441,
                16.1039
            ]
        }
    ]
});
Set styles on ht.Edge
edge.s({
    "flow": true,
    "flow.element.count": 2,
    "flow.element.image": ["dataIcon1", "dataIcon2"],
    "flow.element.max": 20,
    "flow.element.min": 20,
    "flow.element.shadow.visible": false,
    "flow.element.space": 50,
    "flow.element.autorotate": true
});
Enter fullscreen mode Exit fullscreen mode

The effect after configuration:

Image description

Summary

With this, our 2.5D topology diagram is now complete. If you would like to try it out, you can send an email to service@hightopo.com to obtain a free trial SDK to draw your own topology diagrams.

To get more: https://www.youtube.com/hightopo

Top comments (0)