preface

SVG is not just an image format, but because it is an XML-based language, which means it inherits XML’s cross-platform and extensibility, it is a big step forward in graphics reusability. For example, SVG can be embedded in other XML documents, and other XML content can be embedded in SVG documents. Different SVG graphics can be easily combined to form new SVG graphics. The technology used in this Demo based on HTML5 ADAPTS to the needs of the era when only power grid scheduling, power distribution network operation monitoring and power distribution network operation and maintenance control can be achieved on Web SCADA account through mobile terminals. HT is the front-end graphics technology solution with the lowest implementation cost and the highest development and operation efficiency due to the evolution of traditional CS desktop monitoring system in the power industry to the new generation of Web and mobile terminals. SVG vector graphics are familiar, especially in the field of industrial control and telecommunications, but this article is not to create a new editor for drawing SVG diagrams, but a higher level of drawing vector graphics and data binding to this graph.

rendering

http://www.hightopo.com/demo/2deditor/HT-2D-Editor.html

Code implementation

The overall framework

The interface is divided into five parts, namely palette component, Toolbar toolbar, graphView topology component, propertyPane property panel and treeView tree component. Components in these five parts need to be created first. And then put it in the corresponding position:

dataModel = new ht.DataModel(); // Palette = new hT.widget.palette (); // Widget panel toolbar = new hT.widget. toolbar (toolbar_config); // Toolbar g2d = new hT.graph.graphView (dataModel); TreeView = new hT.widget. treeView (dataModel); // Tree component propertyPane = new hT.widget. propertyPane (dataModel); / / attributes panel propertyView = propertyPane getPropertyView (); // Property componentRulerFrame = new hT.widget.rulerFrame (g2d); / / scaleCopy the code

These layouts can be easily completed by combining splitView with borderPane. SplitView is the split component in HT, and parameter 1 is the view component placed in front (which can be left or top). Parameter 2 is the view component placed behind (either to the right or below); Parameter 3 is optional. The default value is H, indicating left-right segmentation; if it is set to V, indicating up-down segmentation. Parameter 4 is the segmentation ratio. BorderPane does something similar to splitView, but in this Demo layout, the code looks cleaner when you combine the two components.

borderPane = new ht.widget.BorderPane(); // Border palette leftSplit = new hT.widget. SplitView(Palette, borderPane,'h', 260); RightSplit = new hT.widget. SplitView(propertyPane, treeView, propertyPane, treeView)'v', 0.4);
mainSplit = new ht.widget.SplitView(leftSplit, rightSplit, 'h'- 260); borderPane.setTopView(toolbar); // Set the top component of the border panel to Toolbar Borderpane.settopHeight (30); borderPane.setCenterView(rulerFrame); // Set the middle component of the frame panel to rulerFrame mainsplit.addtodom (); // Add the underlying div of mainSplit to the body datamodel. deserialize(datamodel_config); // Deserialize the content of datamodel_config and convert the JSON content into the topology scene content g2d.fitContent();Copy the code

After the layout, IT was time to think about what to put in each container. I wrapped these contents into different functions and called these functions to display the data.

Palette Component panel

The Palette component panel on the left needs to add groups inside it as a group, and then add nodes to the group. But one of the most important reasons we use this component is that it can drag and drop nodes, but since we need to generate a new node in the graphView topology component to display on the topology, I wrote the drag logic in the initialization function of the graphView topology component, which is not explained in this section.

While the most important factor is drag and drop, this component is undeniably straightforward in its categorization:

In the picture above, I Palette three items: power, food processing, and sewage treatment. Underneath these groups are many nodes of that group type. I store the grouping information in the palette_config.js file. Due to the large amount of information in the three groups, only a small part of the information is shown here to see how the grouping can be displayed using json objects:

palette_config = {
    scene: {
        name: 'power',
        items: [
            { name: 'words', image: '__text__'.type: ht.Text },
            { name: 'arrows', image: 'symbols/arrow.json' },
            { name: 'ground', image: 'symbols/earthwire.json' }
        ]
    },
    food: {
        name: 'Food processing Plant',
        items: [
            { name: 'Batch fluidized bed processor', image: 'symbols/food/Batch fluid bed processor.json'},
            { name: 'Beer bottle', image: 'symbols/food/Beer bottle.json'},
            { name: 'Bench homogenizer', image: 'symbols/food/Batch fluid bed processor.json'}
        ]
    },
    pumps: {
        name: 'Sewage treatment',
        items: [
            { name: '3 d pump', image: 'symbols/pumps/3-D Pump.json'},
            { name: '18- Wheeler Truck ', image: 'symbols/pumps/18-wheeler truck 1.json'}}};Copy the code

The object is traversed to retrieve internal data, displaying different data information. Of course, to retrieve the object’s information, we need to create the hT. Group object and the hT. Node element inside the Group (which are the children of the Group), and then assign the retrieved data to the two types of nodes and add them to the data container in the Palette:

function initPalette(){// Initialize the contents of the component panelfor(var name inVar info = palette_config[name]; var info = palette_config[name]; var group = new ht.Group(); Ht. Node displays the button element group.setName(info.name); group.setExpanded(false); // Set group to turn off palette.dm().add(group) by default; // Add the node to the data container in the palette info.items. ForEach (function(item){ var node = new ht.Node(); // Create a Node of type hT. Node node.setName(item.name); // Set the name to be displayed in the palette below the node description node.setimage (item.image); // Sets the image // text type of the node to display in the Paletteif(item.type === ht.text) {// Set in the JSON objecttypeNode.s ({node.s: {node.s: {node.s: {node.s: {node.s: {node.s: {node.s: {'text': 'Text'// Nodes of text type need to set this property to display the content of text'text.align': 'center'// Text alignment'text.vAlign': 'middle'// Text is vertically aligned'text.font': '32px Arial'// Text font}); } node.item = item; node.s({'image.stretch': item.stretch || 'centerUniform'// Set the node to display the image in the fill mode, so that the image of different proportions will not be distorted by stretching'draggable': item.draggable === undefined ? true: item.draggable,// sets whether the node can be dragged}); group.addChild(node); // Set the node to palette.dm().add(node); // The node should also be added to the data container in the palette for storage}); }}Copy the code

GraphView topology component

Let’s see how this works by dragging a node from the Palette component into the graphView topology. If the Draggable property of the Node in the Palette is set to True, then the Palette can handle the Dragstart automatically, but the dragover and Dragdrop events need to be handled. We know that dragover and DragDrop events are not supported on IOS and Android devices, so the Palette plugin also provides a simulated drag event handleDragAndDrop, which is perfectly compatible with PCS and handheld devices.

function initGraphView() {if(ht. Default. IsTouchable) {/ / determine whether to Touch can Touch way interaction. The palette handleDragAndDrop =function(e, state) {// Override this method to disable HTML5 native Drag and Drop events and enable emulated Drag eventsif(ht. Default. ContainedInView (e, g2d)) {/ / judge whether the location in the View component interaction eventsif(state === 'between'){ e.preventDefault(); // Cancels the default action for the event. }else if(state === 'end'){// When state is end, determine whether e is in the scope of the graphView, if so, create Node handleDrop(e); }}}; }else{
        g2d.getView().addEventListener("dragover".function(e) {
            e.dataTransfer.dropEffect = "copy";
            e.preventDefault();
        });
        g2d.getView().addEventListener("drop".function(e) { handleDrop(e); }); }}functionHandleDrop (e){// The event e.preventDefault() is triggered when the mouse is released over the target element; var paletteNode = palette.dm().sm().ld(); // Get the last selected node on the Paletteif(paletteNode) { var item = paletteNode.item, image = item.image; data = g2d.getDataAt(e, null, 5); / / get event node under var node = new (item. Type | | ht. Node) (); node.setImage(image); // Set the node image node.setname (item.name); // Set the node name node.p(g2d.lp(e)); // Set the node coordinates to the logical coordinates in the topology. The lp function converts the event coordinates to the logical coordinates in the topology.'label'.' '); // Set the node not to be displayed at the bottom of the graphViewsetDescription in Name. The priority of label is higher than that of nameif(data instanceof ht.Group){// If you drag it to a node of the "Group type", then set the parent relationship node.setparent (data); // Set the parent data.setexpanded (true); // Expand group}else{ node.setParent(g2d.getCurrentSubGraph()); } g2d.dm().add(node); g2d.sm().ss(node); }}Copy the code

I added a JSON scene in the center of the scene in the graphView topology, deserialize a telecom industry drawing exported from the CONTENT of the JSON scene through dM.deserialize (datamodel_config). HT’s unique vector engine function meets the needs of the power industry, such as a wide variety of equipment, infinite scaling of equipment graphics and line network, real-time refresh of binding measurement data, etc. 3D rendering technology makes it possible to monitor power plants, transformers and other equipment with 3D visualization.

TreeView component tree

As for the tree component, the tree component and graphView topology component share the same dataModl data container. Originally, you only need to create a tree component object and add it to the layout container to display all the data nodes in the current topology graph. Generally, HT will divide the nodes on the tree component into several types to display. Ht. Edge, HT. Group, HT. Node, HT. SubGraph, hT. Shape, etc., but there is a problem with this. If you create too many nodes, you can’t tell which Node is which, so you can’t quickly locate and modify the Node. Can cause a lot of trouble for the cartographer, so I do something about the display of the Label and icon in the treeView:

// Initialize the tree componentfunction initTreeView() {// Reload the text on the tree component to display treeView.getLabel =function (data) {
        if (data instanceof ht.Text) {
            return data.s('text');
        }
        else if (data instanceof ht.Shape) {
            return data.getName() || 'Irregular pattern'
        }
        return data.getName() || 'nodes'}; // The icon on the overloaded tree component shows var oldGetIconFunc = treeView.geticon; treeView.getIcon =function (data) {
        if (data instanceof ht.Text) {
            return 'symbols/text.json';
        }
        var img = data.getImage();
        returnimg ? img : oldGetIconFunc.apply(this, arguments); }}Copy the code

PropertyPane property panel

The property panel is a container for displaying properties. Different types of nodes may display different properties, so I store several common types of properties into an array in the properties_config.js file. Text_properties is used to display properties of text nodes, data_properties is used to display properties of all data nodes, node_properties is used to display properties of HT. Node, group_properties Used to display properties of hT. Group nodes and edge_properties to display properties of HT. Edge nodes. By classifying these attributes, we can filter the attributes for the different node types selected in the graphView:

function initPropertyView(){// Initialize the property component datamodel.sm ().ms()function(e) {/ / monitor selected change event propertyView setProperties (null); var data = dataModel.sm().ld(); // Set different properties for different types of nodesif(data instanceof ht. Text) {/ / Text type propertyView. AddProperties (text_properties);return;
        }
        if(data instanceof ht. Data) {/ / data types, all nodes. Based on this type propertyView addProperties (data_properties); }if(data instanceof ht. Node) {/ / the Node type propertyView. AddProperties (node_properties); }if(data instanceof ht. Group) {/ / Group type propertyView. AddProperties (group_properties); }if(data instanceof ht. Edge) {/ / attachment type propertyView addProperties (edge_properties); }}); }Copy the code

Data binding is also shown in the properties bar, with “labels” and “editable” in datA_properties as examples:

{
    name: 'name',// Sets the name attribute. If accessType is not set, the default is get/setName to get and set the value displayName:'name',// Display text value for retrieving attribute name, if empty, display name attribute value editable:true// Set the property to editable}, {name:'2d.editable',//结合 accessType,则通过 node.s('2d.editable'AccessType gets and sets this attribute:'style'// Operation access attribute type displayName:'editable',// Display text value for the property name, if empty display name property value valueType:'boolean',// Boolean type, displayed as check box editable:true// Set the property to editable}Copy the code

These two attributes are typical. One is to set the value of the name attribute directly through get/set, and the other is to control the value of the name attribute by combining the type of the attribute. By manipulating the properties “Name” and “editable” in the property bar, you can see the display of the corresponding node directly in the topology, which is called data binding. Of course, partial data binding for vector graphics is also possible, but it is not the focus of this article. If you are interested, please refer to my article on WebGL 3D Telecom Rack data binding.

The toolbar toolbar

I almost forgot to mention this part, but the Toolbar has eight functions: Select Edit, Wire, Right-angle wire, irregular graphics, scale display, Scene zoom, Scene zoom, and Scene content export JSON. These eight functions are stored in the toolbar_config.js file, and each element is added to the corresponding click-triggered content by drawing elements from the Toolbar. This is the createedgeInteractor.js connection.

We customize the CreateEdgeInteractor class with hT.default. def, SetInteractors ([new CreateEdgeInteractor(graphView, ‘points’)])) Interactive functions can be implemented to create wires.

The CreateEdgeInteractor class adds an Edge wire to the graphView topology by listening for the event after touchEnd is dropped. You can draw different wire types by passing parameters in the CreateEdgeInteractor function. For example, ortho is a broken line:

var CreateEdgeInteractor = function (graphView, type) {
    CreateEdgeInteractor.superClass.constructor.call(this, graphView);   
    this._type = type; }; Ht.default. def(CreateEdgeInteractor, DNDInteractor, {// Custom class, inherit DNDInteractor, this interaction has some basic functions handleWindowTouchEnd:function (e) {
        this.redraw();
        var isPoints = false;
        if(this._target){ var edge = new ht.Edge(this._source, this._target); // Create a line, passing in the starting point and the ending point edge.s({'edge.type'_type// Sets the connection type to the parameter passed intypeType Reference HTforWeb connection type}); isPoints = this._type ==='points'; // The default connection mode is pointsif(isPoints){
                edge.s({
                    'edge.points': [{/ / set the attachment point x: (this) _source) p (). X + enclosing _target. P (). X) / 2, y: (this) _source) p (). Y + enclosing _target. P (). Y) / 2}]}); } edge.setParent(this._graphView.getCurrentSubGraph()); // Set the parent node of the line to the current subnet this._graphView.getDatamodel ().add(edge); / / add attachment to the topology data container enclosing _graphView. GetSelectionModel () setSelection (edge); / / set the node selected} this. _graphView. RemoveTopPainter (this); // Delete the top-level Painterif(isPoints){ resetDefault(); // Reset the toolbar navigation bar status}}});Copy the code

conclusion

A began to say to do the editor is still a little afraid afraid of, is feeling heavy task, but no, not so always drag, but then the overall analysis, found that is really a good step by step, don’t think too complicated steps, what things are from small to large, before we use SVG rendering graphics can be drawn on it, of course, It’s ok to expand if you need to, because someone else’s editor may not be able to meet your requirements. Although the editor on drawing with another, but the most important thing is that it can draw vector graphics, according to the data binding of the HT and animation, we can to every part of the vector graphics, such as flashing lights, such as people blink etc, for these are another story. I was also able to develop much faster with this editor