Serious procrastination. On the one hand, this project module is purely personal entertainment. On the other hand, the flow chart involves a lot of things, this is just a brief introduction of some parts. After all this time, I finally decided to write a “pseudo-tutorial” article on flowchart based on Vue + SVG. For the first time, please spray gently.

Introduction of the module

The project address

Preview picture

For the purpose of learning vUE rather than compatibility, this project only considers modern browsers (Google), please forgive some compatibility problems.

This module was developed from a simple need for flowcharts (pure UI implementation, no business logic at the moment). There is no mention of vuE-CLI-generated directory structures (see this article or Google yourself).

Actual technology stack used in the project: SVG + VUE + VUex

Function introduction:

  • The canvas zoom
  • Nodes (start, base, judgment, etc.) add, delete
  • Connections between nodes (straight/broken lines)
  • Text to add
  • External import of SVG graphics
  • Undo and redo

The canvas zoom

Transform: scale(); transform: scale(); transform-origin: ; The node is positioned relative to the parent layer.

TODO: Optimal scaling solution for SVG?

Node related

Let me give you a brief idea:

Since there is no business logic, I simplify the flowchart to start with a base judgment of three base components (based on SVG).

Such as:

<template>
    <! - began to -- -- >
    <ellipse v-bind="style"></ellipse>
    <! Based -- -- - >
    <rect v-bind="style"></rect>
    <! - judgment -- -- >
    <path v-bind="style"></path>
</template>Copy the code

Here we will talk about the judgment of this component (complex shapes may appear in the later stage will be realized by PATH). Generally, the AI software directly exports the relevant shapes.

The left-hand toolbar is from the same component as the image in the canvas, so there are two styles, defaultStyle and drawStyle. It has been considered that if the diagrams of the flowchart are complex and variable, then each component of the pattern has to be defined manually. Similarly, importing SVG has similar problems. Because if the graphics size is not determined, in addition to supporting graphics to change the size, otherwise will cause the canvas to appear different sizes of graphics. (Too bad we didn’t make a breakthrough in this area, but this is a direction for future improvement.)

The initial solution was to scale, which is to have a uniform scale relationship between the images in the toolbar and those dragged onto the canvas. However, this method also causes the stroke to scale year-on-year, which is not what we want.

So for the time being, write to death is adopted.

Note: In SVG, ellipse is positioned relative to the center point, while RECt is positioned relative to the upper-left corner.

Does TODO have a way to set component location source points as component centerpoints?

Node in the render

In terms of node rendering, graphics are used as components before, so the way of Component + IS is used to render graphics. It is also rendered in a data-driven manner, where the data determines the view.

 <component v-for="(item,index) in nodeData" :is="item.type" :id="item.id" v-node inDraw></component>Copy the code

Drag a node involving a mirror node:

<component :is="selNodeId" :transform="selNodeInfo.transform" v-if="isDragging" inDraw></component>Copy the code

Code through train

The new node

Drag drop. The advantage of this approach is that there is no need to simulate drag events. You don’t have to do it yourself. (Use native simulation for node drag within canvas)

Code through train

The operations on nodes are reported to be in the form of directives (direct DOM manipulation). This leads me to question whether this kind of project is suitable for using vUE class framework. From the aspect of development efficiency, I prefer VUE, but from the aspect of performance, I have no say because I haven’t studied deeply.

TODO scenario simulation, assuming that we need to move the nodes in the canvas, we fetch the nodes using the EL of the directives and then modify the corresponding Translate in the data using el.onMousemove to implement the position change. Here we modify data to drive the view in a common way, but I wonder if el.onMousemove can change the performance of the two-way data binding implemented by Data.

What I’m wondering is whether the performance boost from DIff is of value when it comes to multiple dependencies. So, for example, I have a list, listData in data, and then I have multiple listData associations in the view. It is better to manipulate listData than DOM directly.

Read about vitrualDOM. Diff allows you to manipulate only the changing DOM.

Get SVG size

To get the node size, use getBoundingClientRect, and since we did the scaling function earlier, we need to divide the node size by the scaling to get the correct value.

let obj = el.getElementsByTagName('g') [0]
let w = obj.getBoundingClientRect().width / _this.drawStyle.zoomRate
let h = obj.getBoundingClientRect().height / _this.drawStyle.zoomRate
let wh = {
    width: w,
    height: h
}Copy the code

Code through train

Summary of Node Operations

Since the display of nodes is based on NodeData, adding and deleting nodes is actually adding and deleting NodeData.

The main code

Attachment related

Wires are actually just SVG lines and polylines, which, like nodes, exist as components and drive wire views with lineData. So the adding and removing of the final connection is also an operation on data.

Display of join points

The first is the position of the link point (green far point position). The previous flow chart made based on jquery used div layout, but now it is more difficult to use SVG. Since SVG cannot use position, it cannot locate based on the current element. The method is soil, which uses the graphics size +padding to dynamically obtain the position of 4 points. Because there is a gap between the four wired nodes and the graph nodes, the event cannot fire when the Mouseover is not in the graph or node. Here is a simulation of a region to solve. Due to personal experience, this part of the code is completely imperative. Do not spray

Code through Train Code Through train 2

Join processing

The connection between nodes does two things :(I won’t tell you the details from mousedown to mouseup, see here)

In fact, a lot of people say that algorithms can solve a lot of junk code. Unfortunately, I haven’t grasped the essence of it, like the graphic components before, and the different lines that follow. You can actually figure it out by some algorithm. I’m only going to talk about the dumbest ones, and I’ll come back to this article when I’m old enough to use algorithms to talk.

  • The line straight

A line is nothing more than the coordinates of two points, shown by line in SVG. At this point, depending on the requirements of the project, we assume that the simplest case is where the four connection points mentioned above start or end the connection. Here are the coordinates of the four points in the figure


computeLine(direction, obj) { // Low is more than a little bit
    let { top, left, width, height } = obj
    let w = width / 2
    let h = height / 2
    switch (direction) {
    case 't':
        top = top - h
        break
    case 'b':
        top = top + h
        break
    case 'l':
        left = left - w
        break
    case 'r':
        left = left + w
        break
    default:
        break
    }
    return { top, left }
}Copy the code
  • Polyline line

Polylines are a little bit more important. Here, because we’re using polyline, the points are longer like this. Points =”125,96, 183.5,96, 242,399″

At this point, characters are converted into arrays or objects that are easier to manipulate. A polyline involves the same starting and ending points as the line described above, but the difference is where the middle line is, and if you don’t care about the complications,

It can be divided into two kinds, up and down, left and right. The midline is determined by taking the midpoint of the starting and ending points and you get the desired polyline. Here’s the code :(all in a crude way.)

computePolyLine(start, end, direction) {
    let startPoint = {
    x: +(start.split(', ') [0]),
    y: +(start.split(', ') [1])}let endPoint = {
    x: +(end.split(', ') [0]),
    y: +(end.split(', ') [1])}let m1, m2
    switch (direction) {
    case 't':
    case 'b':
        let mY = startPoint.y + (endPoint.y - startPoint.y) / 2
        m1 = {
        x: startPoint.x,
        y: mY
        }
        m2 = {
        x: endPoint.x,
        y: mY
        }
        break
    case 'l':
    case 'r':
        let mX = startPoint.x + (endPoint.x - startPoint.x) / 2
        m1 = {
        x: mX,
        y: startPoint.y
        }
        m2 = {
        x: mX,
        y: endPoint.y
        }
        break
    default:
        break
    }
    return `${startPoint.x}.${startPoint.y} ${m1.x}.${m1.y} ${m2.x}.${m2.y} ${endPoint.x}.${endPoint.y}`
}Copy the code

Attachment to summarize

Nodes and wires are pretty much the same in how they are rendered and manipulated. There are two areas where it’s not a good practice to render components as a Component + IS and manipulate the DOM as a Diretives. The calculation form of wiring is also slightly simpler, which does take some time to grow. Just to summarize, no matter what the wiring is, all we need to do is get the correct position of the corresponding points, and then modify the data to drive the view. But it’s a leap to be able to generalize algorithms in all sorts of complicated situations. Go for it.

Text addition of nodes and wires

The principle for adding text to nodes and wires is the same, with contenteditable being set to true, the HTML structure automatically adds text nodes and is editable. More details can be found in This article by Zhang Xinxu

By the way, this module uses the pointer-Events CSS property in two places. One is the text add area, and the header toolbar area.

The CSS attribute pointer-Events allows the author to control when a specific graphic element becomes the target of a mouse event. When this property is not specified, SVG content behaves like visiblePainted.

In addition to specifying that the element is not the target of the mouse event, the value None also indicates that the mouse event passes through the element and points to the element below it.

More details on pointer-Events

Zhang Xinxu MDN

TODO text editing has been implemented, but it has many bugs and is not yet perfect.

Importing SVG externally

HTML5 Drop function is also used here, and SVG images are used for display. Drag and drop implementation is simpler:

dropHandle (e) {
    let reader = new FileReader()
    let file = e.dataTransfer.files[0]
    reader.onload = (e) = > {
        this.userImages.push(e.target.result)
    }
    reader.readAsDataURL(file)
},
dragoverHandle () {
},
dragstart (imgSrc) {
    event.dataTransfer.setData('URL', imgSrc)
}Copy the code

Stop =”dropHandle” @dragover. Stop. Prevent =”dragoverHandle” to prevent bubbling and browser default behavior.

There is a note dataTransfer. GetData () in the dragover, dragenter, dragleave in the problem of unable to get data

According to W3C standards, drag data store has three modes: Read/write mode, Read-only mode, and Protected mode. details

Read/ Write Mode Read/write mode, used in dragstart events to add new data to the Drag Data store.

Read-only mode Read-only mode. This mode is used in drop events to Read dropped data but not add new data.

Protected mode, used in all other events, the list of data can be enumerated, but the data itself is not available and new data cannot be added.

in-depth

Undo and redo

This functionality is essentially incomplete because vuex takes a lazy approach to generating State snapshots, which is not recommended for production environments.

The basic principle is to trigger a callback via VUEX commit higher (mutation). To record the state snapshot

Code through train

conclusion

This project is an entry level vUE + VUex, but it does not explain how to use VUE or VUex, because these have been explained very clearly in the official documents. This project simply uses common methods such as vue custom instructions, mixins and so on. Components such as vue Render function are beyond the scope of this article. Here is a brief description of the use experience. Render component is suitable for highly customized components (change logic is more complex). Because some simple components are better suited to tempalte, using Render can improve performance (reducing the step from tempalte to Render), but many existing components, such as Sync, are not available (need to implement). When using vuex, you need to pay attention to the problem of object reference address. In other words, avoid potential impacts between data. (although vuex itself circumvents this problem) learn about immutable.

This tutorial focuses on how to implement a simple flowchart based on VUE, and raises questions about which projects are best suited for using this MVVM pattern and how to leverage the value of VitrualDOM. In fact, the points in the above chapters can be taken out of the in-depth discussion of a lot of technical problems, later have the opportunity to continue to in-depth.