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