Using zrender to realize the graphical design of workflow (with example code)

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 ), 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,


    this.type = type;

    this.addActivity = function (activity) {

        activity.graph = graph;

        activities[] = { object: activity };


    this.getActivity = function (id) { return activities[id].object; };

    this.addTransition = function (transition) {

        transition.graph = graph;

        transitions[] = { 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 =;

        for (var key in transitions) {

            var transition = transitions[key].object;

            if (transition.from === activityId || == activityId) {





    //Currently selected part

    var selectedUnit = null;

    this.onUnitSelect = function (unit) {

        if (selectedUnit) zrenderInstance.refreshShapes(modElements(selectedUnit.unselect(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) {


        if (currentUnit) currentUnit.showContextMenu(event, contextMenuContainer, graph);


    this.addShape = function (shape) {




    this.init = function () {

        var canvasElement = options.canvas.element;


        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' });



        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)) {




        //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' });





  * 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.title = options.title;


    createShapeOptions: function () {

        var _this = this;

        return {

            hoverable: true,

            clickable: true,

            onclick: function (params) {

                //Select and highlight



            onmouseover: function (params) { _this.graph.onUnitMouseOver(_this); },

            onmouseout: function (params) { _this.graph.onUnitMouseOut(_this); }



    addTo: function (graph) { },

    //Refresh display

    refresh: function (graph) { return []; },


    select: function (graph) {

        this.selected = true;

        return this.refresh(graph);



    unselect: function (graph) {

        this.selected = false;

        return this.refresh(graph);


    //Show context menu

    showContextMenu: function (event, container, graph) {


        container.innerHTML = '';

        var ul = new Element('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)

   = document.body.scrollLeft + event.clientX - container.offsetWidth + offset;


   = document.body.scrollLeft + event.clientX + offset;

        if (bottomEdge < container.offsetHeight)

   = document.body.scrollTop + event.clientY - container.offsetHeight + offset;


   = document.body.scrollTop + event.clientY + offset;



    //Create context menu item

    buildContextMenuItems: function (container, graph) {

        var unit = this;

        unit.contextMenuItems.each(function (item) {





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.

Transfer from



Keywords: Front-end canvas

Added by treybraid on Fri, 18 Feb 2022 20:00:56 +0200