The management system developed by the company has the function of workflow graphical design and viewing, which has a long history of development. In those dark days, IE almost unified the Jianghu, so it was logical to adopt the VML technology, which was very popular at that time.
Later, as we all know, IE began to decline, and VML technology has long been extinct, resulting in the original workflow graphical function can not be used at all, so we need to adopt new technology to rewrite the workflow graphical function.
After comparison, we decided to implement it with zrender Library (for the introduction of zrender library, please see http://ecomfe.github.io/zrender/ ), it took a day to finally make a general effect model, as shown in the figure below:
The flow chart is composed of two types of parts: moving parts and connecting arc parts. Each type of parts contains multiple parts with different properties.
Take the moving parts as an example. The circular part is the starting part, the parallelogram is the automatic part, and the rectangular part is the manual part, etc.
In terms of code implementation, Unit (component base class) is defined, and all components inherit from this base class. Graph class is used to manage the whole flow chart, including all parts, context menus, etc., which are uniformly managed and scheduled by graph. The code is as follows:
var Libra = {}; Libra.Workflow = {}; Libra.Workflow.Graph = function (type, options) { var graph = this, activities = {}, transitions = {}; var zrenderInstance, contextMenuContainer; this.type = type; this.addActivity = function (activity) { activity.graph = graph; activities[activity.id] = { object: activity }; }; this.getActivity = function (id) { return activities[id].object; }; this.addTransition = function (transition) { transition.graph = graph; transitions[transition.id] = { object: transition }; }; function modElements(shapes) { shapes.each(function (shape) { zrenderInstance.modElement(shape); }); return shapes; } //Node currently being dragged and dropped var dragingActivity = null; //Active node drag and drop start this.onActivityDragStart = function (activity) { dragingActivity = activity; }; //Active node drag and drop end this.onActivityDragEnd = function () { if (dragingActivity) refreshActivityTransitions(dragingActivity); dragingActivity = null; }; //Drag process processing function zrenderInstanceOnMouseMove() { if (dragingActivity != null) refreshActivityTransitions(dragingActivity); } //Refresh all connection arcs related to the activity function refreshActivityTransitions(activity) { var activityId = activity.id; for (var key in transitions) { var transition = transitions[key].object; if (transition.from === activityId || transition.to == activityId) { zrenderInstance.refreshShapes(modElements(transition.refresh(graph))); } } } //Currently selected part var selectedUnit = null; this.onUnitSelect = function (unit) { if (selectedUnit) zrenderInstance.refreshShapes(modElements(selectedUnit.unselect(graph))); zrenderInstance.refreshShapes(modElements(unit.select(graph))); selectedUnit = unit; }; //Record which part the current mouse is on, which can be used to generate context sensitive menus var currentUnit = null; this.onUnitMouseOver = function (unit) { currentUnit = unit; }; this.onUnitMouseOut = function (unit) { if (currentUnit === unit) currentUnit = null; }; //Context menu event response function onContextMenu(event) { Event.stop(event); if (currentUnit) currentUnit.showContextMenu(event, contextMenuContainer, graph); } this.addShape = function (shape) { zrenderInstance.addShape(shape); }; //Initialization this.init = function () { var canvasElement = options.canvas.element; canvasElement.empty(); canvasElement.setStyle({ height: document.viewport.getHeight() + 'px' }); zrenderInstance = graph.type.zrender.init(document.getElementById(canvasElement.identify())); for (var key in activities) { activities[key].object.addTo(graph); } for (var key in transitions) { transitions[key].object.addTo(graph); } //Create context menu container contextMenuContainer = new Element('div', { 'class': 'context-menu' }); contextMenuContainer.hide(); document.body.appendChild(contextMenuContainer); Event.observe(contextMenuContainer, 'mouseout', function (event) { //When closing, judge whether the mouse has moved out of the menu container if (!Position.within(contextMenuContainer, event.clientX, event.clientY)) { contextMenuContainer.hide(); } }); //Listen to drag process zrenderInstance.on('mousemove', zrenderInstanceOnMouseMove); //Context menu Event.observe(document, 'contextmenu', onContextMenu); }; //Render or refresh rendering this.render = function () { var canvasElement = options.canvas.element; canvasElement.setStyle({ height: document.viewport.getHeight() + 'px' }); zrenderInstance.render(); }; }; /* * Components (including moving and connecting arcs) */ Libra.Workflow.Unit = Class.create({ id: null, title: null, graph: null, //Is it currently selected selected: false, //Context menu item collection contextMenuItems: [], initialize: function (options) { var _this = this; _this.id = options.id; _this.title = options.title; }, createShapeOptions: function () { var _this = this; return { hoverable: true, clickable: true, onclick: function (params) { //Select and highlight _this.graph.onUnitSelect(_this); }, onmouseover: function (params) { _this.graph.onUnitMouseOver(_this); }, onmouseout: function (params) { _this.graph.onUnitMouseOut(_this); } }; }, addTo: function (graph) { }, //Refresh display refresh: function (graph) { return []; }, //Select select: function (graph) { this.selected = true; return this.refresh(graph); }, //Uncheck unselect: function (graph) { this.selected = false; return this.refresh(graph); }, //Show context menu showContextMenu: function (event, container, graph) { container.hide(); container.innerHTML = ''; var ul = new Element('ul'); container.appendChild(ul); this.buildContextMenuItems(ul, graph); //Add an offset so that the mouse is in the menu var offset = -5; var rightEdge = document.body.clientWidth - event.clientX; var bottomEdge = document.body.clientHeight - event.clientY; if (rightEdge < container.offsetWidth) container.style.left = document.body.scrollLeft + event.clientX - container.offsetWidth + offset; else container.style.left = document.body.scrollLeft + event.clientX + offset; if (bottomEdge < container.offsetHeight) container.style.top = document.body.scrollTop + event.clientY - container.offsetHeight + offset; else container.style.top = document.body.scrollTop + event.clientY + offset; container.show(); }, //Create context menu item buildContextMenuItems: function (container, graph) { var unit = this; unit.contextMenuItems.each(function (item) { item.addTo(container); }); } });
zrender supports drawing dragging by default, so you only need to set the dragable attribute to true for dragging of active parts. However, although the movable part can be dragged, the connecting line on the movable part will not move together. This requires listening for drag start events, drag end events and mouse movement events in the process of dragging to realize the real-time redrawing of the connecting line. Listening for mouse movement events in Graph is to realize the real-time redrawing of related graphics such as connecting lines.
Each part has eight connection points planned. By default, the connection arc is not fixed with a connection point, but automatically finds the nearest connection point according to the position relationship of the active part. Therefore, when dragging the active part, you can see that the connection point of the connection arc on the active part is constantly changing.
The above only realizes the basic functions of workflow graphical design in the most simplified way. The perfect graphical design should include the drag and drop of curves and connection points, as shown in the following figure:
The above is the workflow graphical design function in the company's products. Compared with the above examples, the function should be much improved, but the basic principle remains unchanged, which is nothing more than dealing with more details.
In particular, I spent a lot of time in the painting place, and almost forgot the plane geometry knowledge in middle school, so I spent a lot of effort in doing it. This part is prepared to write an article in detail in the future.
The end of this article will give the complete code download of the early modeling and testing stage. It is the early code, not the final code. You know the reason. Forgive me.
http://files.cnblogs.com/files/rrooyy/WorkflowGraphic.zip