Before reading this article, we recommend reading: [Learning Notes] Hightopo’s HT for Web (1) - Basic Conceptions
Using "HT for Web" (or simply "HT") for visualization is mainly divided into two parts: 2D and 3D. These two parts need to be created separately. After they are created, we then integrate them together.
The 2D part of "HT for Web" primarily refers to ht.graph.GraphView (abbreviated as GraphView, which is the 2D drawing or 2D display). The essence of a 2D display is a canvas. We can draw and edit basic shapes on it, create connection layouts, or render animations. GraphView can be used independently from 3D, such as for creating regular web pages, SCADA, charts, flowcharts, etc.
In this section, we will use an example to explain the basic concepts, functions, and usage of 2D displays. It mainly includes the following parts:
1. Creating a 2D display - (GraphView)
2. Adding nodes - (ht.Node)
3. Coordinate system and coordinate conversion
4. Adding connections - (ht.Edge)
5. Animation - (ht.Default.startAnim())
Creating a 2D display - (GraphView)
You can create a 2D display by using command “new ht.graph.GraphView()”. After creating the graph, for HT components, you can add it to the DOM using the “GraphView.addToDOM()” method. The addToDOM() method itself accepts one parameter. If it's empty, it will be added to the body by default. You can also pass a div to fix the 2D graph view to a specific position on the page.
For newly created graph view, they have default properties such as zooming, panning, editing, and rectangular selection. We can enable or disable them as needed.
Like other view components, if no DataModel is specified, GraphView itself will also create an empty DataModel container to manage all kinds of graphic elements added to it. You can get this container through GraphView.getDataModel() or GraphView.dm(). For GraphView, you can use the method “DataModel.setBackground()” to change its background.
/*************** Create one 2D display and append it to body element ******************/
const gv = new ht.graph.GraphView();
gv.addToDOM();
gv.setZoomable(true); // Default: true
gv.setPannable(true); // Default: true
gv.setEditable(true); // Default: true
gv.setRectSelectable(true); // Whether to allow box selection for Nod, default: true
const dm = gv.getDataModel(); // Get the DataModel of the drawing, abbreviated form: [gv.dm](http://gv.dm/)()
dm.setBackground('#DADADA'); // Also support dm.setBackground('rgba(218, 218, 218, 1)');
Why do we set the background of the graph view through DataModel instead of directly operating on the GraphView itself?
In the first section[[Learning Notes] Hightopo’s HT for Web (1) - Basic Conceptions], we mentioned that in order to save and restore our created 2D/3D data, we can achieve this through serialization and deserialization of the DataModel. Note that this serialization operation targets the DataModel, not the view component. Therefore, for view components like GraphView, display properties such as background color need to be configured in the DataModel in order to be saved.
Adding nodes - (ht.Node)
With the DataModel of the 2D graph view, we can now add nodes or graphic elements to it. Here we add two server cabinet icons.
/**************** Create two HT nodes and add them to the 2d display ************************/
const server1 = new ht.Node();
server1.setSize(40, 100); // Node width and height. Should be set according to the scale of the picture, otherwise there will be a stretching effect
server1.setPosition(100, 100); // Node location. The top left corner is the (0,0) coordinate
server1.setImage('assets/server.png'); // Image
server1.setName('Server 1'); // Node Name
dm.add(server1); // Add to the DataModel, that is, add to the 2d display
const server2 = new ht.Node();
server2.setSize(40, 100);
server2.setPosition(250, 100);
server2.setImage('assets/server.png');
server2.setName('Server 2');
dm.add(server2); // Add to the DataModel, that is, add to the 2d display
In traditional frontend (Vue, React, HTML) page development, to add a node, people often need to manually create the node (such as adding an icon) and add it to the HTML. Then we operate on that node through data binding. However, in HT, we only need to add the new node to the DataModel. The GraphView will listen for changes in the DataModel and automatically trigger re-rendering operations. This way, all style configurations and operations on the node can be performed directly on the node, and GraphView will update automatically.
Config the ht.Node
ht.Node (abbreviated as Node) is the basic class for rendering node elements in 2D drawings and 3D scenes, inheriting from ht.Data. Building on the foundation of ht.Data, it adds attributes such as position, size, rotation, scaling, and adsorbing. These attributes can all be set and retrieved through set*/get* methods.
In GraphView, when adding a new ht.Node, it will display a computer image by default. We can change this using the setImage(image url) method. As in the code example above, we got a cabinet photo and assigned it to the newly added node.
Since the cabinet image might be very large, we can control the node size using setSize(width, height). By configuring different width and height values, we can also achieve stretching effects. If you don't want stretching effects, you can set the node size according to the original image's aspect ratio.
Coordinate System and Coordinate Transformation
If a position is not specified for an added node, it will be placed at the point (x: 0, y: 0) by default. The node's position can be controlled through Node.setPosition(x, y) or Node.p(x, y). Its position can be obtained through Node.getPosition() or Node.p().
Since GraphView itself has properties like zooming and panning, the position of its nodes displayed in the browser is not fixed. Therefore, we can understand that GraphView's coordinate system is different from the browser's coordinate system.
In fact, GraphView uses a relative coordinate system, with direction consistent with canvas, meaning the top-left corner is the (0, 0) point. The positive direction of the x-axis is to the right, and the positive direction of the y-axis is downward. The following two methods can be used to convert between GraphView coordinates and browser coordinates.
- GraphView.getLogicalPoint(event): Passes in an HTML event object to convert browser coordinates to logical coordinates in GraphView
- GraphView.getScreenPoint(point, y): Passes in coordinates from GraphView to convert them to browser coordinates
Adding edges- (ht.Edge)
After adding two server cabinet icons, we will now connect them with lines. In HT, connections are implemented using ht.Edge.
The ht.Edge type (abbreviated as Edge) is used to connect a source and target Node. Multiple Edges can exist between two nodes, and it's also allowed for the source and target to be the same node. You can pass the source and target node objects directly in the constructor using new ht.Edge(source, target), or you can build the Edge object first and then set them separately.
getSource() and setSource(node) get and set the source node
getTarget() and setTarget(node) get and set the target node
isLooped() determines whether the line's source and target are the same node
/****************************** Create Edges *************************************/
const edges = [];
// Create 3 edges connecting server1 and server2
edges.push(createEdge(4, 'green', 6, 'yellow', [20, 10]));
edges.push(createEdge(3, '#fff', 3, '#000', [10, 10])); // black white edge
edges.push(createEdge(10, 'rgb(51,153,255)', 5, 'rgb(242,83,75)', [5, 10])); // red blue edge
function createEdge(width, color, dashWidth, dashColor, pattern) {
const edge = new ht.Edge(server1, server2);
edge.s({
'edge.width': width,
'edge.gap': 30, // gap between edges
'edge.color': color,
'edge.dash': true,
'edge.dash.width': dashWidth
'edge.dash.color': dashColor,
'edge.dash.pattern': pattern,
'edge.offset': 0,
});
dm.add(edge); // remember to add it to the dataModel
return edge;
}
// Create the 4th one to connect server 1 itself
const edge = new ht.Edge();
edge.setSource(server1);
edge.setTarget(server1);
edge.s({
'edge.width': 5,
'edge.gap': 30,
'edge.color': 'pink',
'edge.dash': true,
'edge.dash.width': 5,
'edge.dash.color': 'purple',
'edge.dash.pattern': [10, 10],
'edge.offset': 0,
});
dm.add(edge);
edges.push(edge);
The code above creates 4 Edges, with the first three connecting server1 and server2, and the fourth one connecting server1 to itself. The properties of ht.Edge are mainly configured through the edge.s() method, which is a shorthand form of edge.setStyle(). The edge.s() method is primarily used to configure the node's built-in properties. The HT for Web engine renders different effects based on the property key-value pairs. If you want to set custom properties, you need to use edge.a() to implement this.
Similar to ht.Node, ht.Edge is also extended from ht.Data. However, in most cases, it has its own characteristics:
- It needs to set a source node and a target node. These two can be the same node (as in the last Edge in the example above).
- An Edge missing either a source or target node will not be displayed on the drawing.
- Edge follows the movement of nodes. That is, when we drag the source and target nodes, their associated Edges will move along with them.
- Since the position of an Edge is determined by its two endpoints, Edge does not support the getPosition()/setPosition() methods. Similarly, as its width is configured in edge.s(), Edge also does not support the getSize()/setSize() methods.
In addition to the configurations in the example above, ht.Edge also supports custom line types. If you find it cumbersome, you can also use one of the dozen or so built-in line types in HT, such as edge.s('edge.type', 'boundary') which means the line only connects to the rectangular edge of the graphic element. If you want to use built-in line types, you need to import the edge type plugin:
<script src="../../lib/plugin/ht-edgetype.js"></script>
Animation - ht.Default.startAnim()
To implement animation functionality, it essentially involves these key properties: the animation duration, property changes during playback, and callback events after completion.
HT supports multiple ways to implement animations. Here we'll choose one of the more commonly used methods. Let's look at the code:
/****************************** Edge Animation *************************************/
const animParams = {
// frames: 12, // frame counts
// interval: 10, // interval between frames
duration: 2000, // total duration
easing: function(t){ return t * t; }, // Easing function, which defaults toht.Default.animEasing
finishFunc: function(){
ht.Default.startAnim(animParams);
}, // The function that is called after the animation finished.
action: function(v, t){ // The action function must be provided to implement property changes during animation. v represents the value calculated by the easing(t) function, and t represents the current progress of the animation, ranging from [0 to 1]
edges.forEach((edge, index) => {
const direction = index%2 == 0 ? 1 : -1;
edge.s('edge.dash.offset', t * 20 * direction);
});
}
};
ht.Default.startAnim(animParams);
Here, the method used by HT for playing animations is ht.Default.startAnim(animParams), which returns an anim object that can be used to call anim.stop(true) to terminate the animation. The anim object also has anim.pause() and anim.resume() methods that can be used to interrupt and continue animation functionality, as well as the anim.isRunning() function to determine whether an animation is in progress.
The parameters (animParams) used by this method are quite simple:
- duration: Animation playback duration. If we want to precisely control the animation frame count and frame interval, we can also use frames + interval to control animation playback.
- easing: Easing function. Uses mathematical formulas to control the speed and pace of animation playback. Refer to http://easings.net/.
- finishFunc: Callback after playback. This method will be executed when the animation ends.
- action: Action control. Which properties of the node need to change are defined here, such as controlling the node's position, rotation, size, etc. This method is called once for each frame during animation execution. The two parameters are:
- t: Represents the current progress of the animation. Its range is from 0 at the start to 1 at the end. This value changes relatively uniformly as time progresses. If we want the animation to execute at a constant speed, we can use t to achieve this.
- v: Represents the value calculated by the easing(t) function. Its range in most cases is also from 0 to 1. But the change in v value is determined by the easing(t) function and is not necessarily uniform. For some easing effects, such as easeOutBack() , its v value may exceed 1 during the middle stage. If you want to use easing effects in animations, you need to use the v parameter to control property changes.
Summary
In this section, we have primarily introduced the creation and basic configuration of HT for Web displays. In GraphView, we can add ht.Node to its DataModel to draw cabinets. The position of nodes can be controlled through Node.setPosition(x, y) or Node.p(x, y) methods, and their coordinates can be obtained through Node.getPosition() or Node.p() methods. Additionally, the coordinate system in GraphView differs from the browser's coordinate system; we can use GraphView.getLogicalPoint(event) and GraphView.getScreenPoint(point, y) methods to convert between these two coordinate systems.
For connections, we use the ht.Edge type. When creating an ht.Edge object, we need to pass in the source and target nodes, and can configure its properties through the edge.s() method, such as width, color, dashed lines, etc. After creating multiple connections, we can use the ht.Default.startAnim(animParams) method to implement connection animations, where animParams is an object containing parameters such as animation duration, easing function, animation end callback function, and action control function.
Top comments (0)