Maison > interface Web > js tutoriel > Résumé de l'expérience de l'organigramme jsPlumb

Résumé de l'expérience de l'organigramme jsPlumb

巴扎黑
Libérer: 2018-05-17 14:03:43
original
7208 Les gens l'ont consulté
在使用jsPlumb过程中,所遇到的问题,以及解决方案,文中引用了《数据结构与算法JavaScript描述》的相关图片和一部分代
码.截图是有点多,有时比较懒,没有太多的时间去详细的编辑.
Copier après la connexion

Avant-propos

D'abord le diagramme de classes UML
Résumé de lexpérience de lorganigramme jsPlumb

Ensuite, l'organigramme
Résumé de lexpérience de lorganigramme jsPlumb

En utilisant les fonctions associées de jsPlumb, vous pouvez voir le prototype de la première version. Cela a pris près de deux mois, entrecoupés d'autres travaux par intermittence, mais les fonctions de base étaient toujours terminées.

En fait, après avoir terminé le travail, j'ai constaté que seule une petite partie des fonctions de jsPlumb était utilisée, et plus encore la compréhension et la mise en œuvre de la structure interne des données. On peut seulement dire que les données sont mises à jour de manière synchrone, et il y a toujours un. une certaine distance du lecteur de données.

Ici, nous résumerons et enregistrerons les problèmes rencontrés dans le projet et les solutions. S'il existe une meilleure méthode, veuillez l'indiquer

Pour le traitement. de plusieurs étiquettes sur la connexion

Comme le montre l'image ci-dessus, j'ai d'abord pensé à configurer deux superpositions lors de la connexion

    var j = jsPlumb.getInstance();

    j.connect({
        source:source,
        target:target,
        overlays:[
            "Arrow",
            ["label",{label:"foo1",location:0.2Résumé de lexpérience de lorganigramme jsPlumb,id:"m1"}],
            ["label",{label:"foo2",location:0.Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb,id:"m2"}]
        ]
    })
Copier après la connexion

Bien sûr, il y a des pièges ici si l'ID. est répété, alors il sera utilisé. Le dernier ne se chevauchera pas, y compris les données mises en cache dans jsPlumb, seul le dernier sera laissé

Plus tard, j'ai découvert que les éléments de configuration peuvent également être modifiés dynamiquement via le. importDefaults fonction.

    j.importDefaults({
        ConnectionOverlays: [
            ["Arrow", { location: 1, id: "arrow", length: 10, foldback: 0, width: 10 }],
            ["Label", { label: "n", id: "label-n", location: 0.2Résumé de lexpérience de lorganigramme jsPlumb, cssClass: "jspl-label" }],
            ["Label", { label: "1", id: "label-1", location: 0.Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb, cssClass: "jspl-label" }]
        ]
    })
Copier après la connexion

C'est juste que, seules les deux étiquettes peuvent être affichées dans la connexion après avoir exécuté la fonction, et les précédentes ne peuvent pas être modifiées ensemble
Donc pour plus de commodité, juste. donnez-les dans l'initialisation Modifié.

Utilisation des groupes

Le groupe est en effet un problème lors de la création d'organigrammes dans le niveau d'imbrication infinie comme indiqué ci-dessus, la fonction Groups fournie par jsPlumb ne peut pas. être utilisé.
Selon la documentation, si un élément est marqué comme groupe, les éléments du groupe se déplaceront avec le mouvement du groupe, tout comme les connexions, mais le problème est qu'une fois qu'un élément devient un groupe , il ne peut pas accepter d'autres éléments groups, en d'autres termes, la méthode Groups qu'elle fournit n'a qu'une seule couche, qui ne peut naturellement pas répondre aux exigences
Tout d'abord, publiez l'utilisation résumée des groupes :

    j.addGroup({
        el:el,
        id:"one"
        constrain:true, // 子元素仅限在元素内拖动
        droppable:true, // 子元素是否可以放置其他元素
        draggable:true, // 默认为true,组是否可以拖动
        dropOverride:true ,// 组中的元素是否可以拓展到其他组,为true时表示否,这里的拓展会对dom结构进行修改,而非单纯的位置移动
        ghost:true, // 是否创建一个子元素的副本元素
        revert:true, // 元素是否可以拖到只有边框可以重合
    })
Copier après la connexion
<🎜. >Plus tard, une nouvelle méthode est adoptée Méthode, lorsque le nœud bouge, actualisez dynamiquement la connexion

    j.repaintEverything();
Copier après la connexion
Afin de ne pas bloquer la page, vous devez utiliser la fonction de limitation

throttle()

    function throttle(fn,interval){
        var canRun = true;

        return function(){
            if(!canRun) return;
            canRun = false;
            setTimeout(function(){
                fn.apply(this,arguments);
                canRun = true;
            },interval ? interval : Résumé de lexpérience de lorganigramme jsPlumb00);
        };
    };
Copier après la connexion
Il s'agit d'une implémentation simple. La méthode consiste principalement à réduire le nombre d'événements appelés à plusieurs reprises lors du déplacement d'événements dans le dom, et en même temps à atteindre l'objectif d'exécuter des événements (permettre uniquement à une fonction d'être exécutée une fois dans x millisecondes);

Bien sûr, vous pouvez également utiliser le trait de soulignement.js intégré. La fonction
peut également atteindre cet objectif _.throttle()

La structure html utilise ici des niveaux imbriqués, ainsi que le parent et l'enfant. les niveaux sont enregistrés dans la source de données interne à l'aide de ce niveau

Analyse de structure de données multicouche ou monocouche

Résumé de lexpérience de lorganigramme jsPlumb

Il existe deux façons de gérer les corps de données comme celui-ci qui ont en fait des relations imbriquées,

  • Imbrication multi-niveaux : similaire à

        [
            {
                id:"1",
                child:{
                    id:"2",
                    child:{
                        id:"Résumé de lexpérience de lorganigramme jsPlumb",
                        child:{}
                    }
                }
            }
        ]
    Copier après la connexion
    , si elle est utilisée pour la gestion, l'avantage est qu'elle est intuitive, vous pouvez connaître la structure globale approximative en fonction du niveau, et il est également très pratique de convertir en XML ou en HTML

    Mais l'inconvénient est qu'il n'est pas si pratique de rechercher et de modifier

    <. 🎜>
  • Afficher tous les nœuds sur une seule couche : similaire à
  • Les avantages de cette structure sont tous sur un seul niveau, il est très pratique de rechercher et de modifier les données si vous souhaitez les analyser. dans une structure multi-niveaux, il vous suffit d'utiliser la récursivité pour générer une nouvelle structure :
        [
            {
                id:"1",
                child:[{
                    id:"2"
                }]
            },
            {
                id:"2",
                parentId:"1",
                child:[{
                    id:"Résumé de lexpérience de lorganigramme jsPlumb"
                }]
            },
            {
                id:"Résumé de lexpérience de lorganigramme jsPlumb",
                parentId:"2",
                child:[]
            }
        ]
    Copier après la connexion

    Initialiser le tableau d'un niveau La fonction
    function mt(){
        var OBJ;
        this.root = null;
        this.Node = function(e) {
            this.id = e.id;
            this.name = e.name;
            this.parentId = e.parentId;
            this.children = [];
        };
    
        this.insert=function(e,key){
            function add(obj,e){
                if(obj.id == e.parentId){
                    obj.children.push(e);
                } else {
                    for (var i = 0; i < obj.children.length; i++) {
                        add(obj.children[i], e);
                    }
                }
            }
    
            if (e != undefined) {
                e = new this.Node(e);
            } else {
                return;
            }
    
            if (this.root == null) {
                this.root = e;
            } else {
                OBJ = this.root;
                add(OBJ, e);
            }
        }
    
        this.init = function(data){
            var _this = this;
            for(var i = 0;i<data.length;i++){
                _this.insert(data[i]);
            }
    
            return OBJ;
        }
    }
    Copier après la connexion
    peut être convertie en une structure multi-niveaux

    init
    Résumé de lexpérience de lorganigramme jsPlumb

  • Si vous souhaitez le convertir en structure HTML, il vous suffit de modifier légèrement la fonction et vous pouvez y parvenir.

Vérifiez s'il existe un impasse dans le processus (qu'il y ait un point sur le chemin qui ne peut pas atteindre la fin du graphique)

Ceci est entièrement réalisé par les algorithmes Tout d'abord, la compréhension du graphique est la clé

<. 🎜>


Je suis trop paresseux pour taper, je vais donc l'exprimer directement avec une image. L'image de base est à peu près comme ceci, et la forme d'expression spécifique est Résumé de lexpérience de lorganigramme jsPlumb

<. 🎜>Vous pouvez voir, la représentation graphique de base peut être représentée par une liste de contiguïté
Résumé de lexpérience de lorganigramme jsPlumb et pour l'implémentation, vous pouvez voir le code suivant :

Et la construction seule n'est pas assez, alors jetons un coup d'œil aux méthodes de recherche de base :

Recherche en profondeur d'abord et recherche en largeur d'abord ;

function Graph1(v) {
  this.vertices = v; // 总顶点
  this.edges = 0; // 图的边数
  this.adj = [];

  // 通过 for 循环为数组中的每个元素添加一个子数组来存储所有的相邻顶点,[并将所有元素初始化为空字符串。]?
  for (var i = 0; i < this.vertices; ++i) {
    this.adj[i] = [];
  }

  /**
   * 当调用这个函数并传入顶点 v 和 w 时,函数会先查找顶点 v 的邻接表,将顶点 w 添加到列表中
   * 然后再查找顶点 w 的邻接表,将顶点 v 加入列表。最后,这个函数会将边数加 1。
   * @param {[type]} v [第一个顶点]
   * @param {[type]} w [第二个顶点]
   */
  this.addEdge = function(v, w) {
    this.adj[v].push(w);
    this.adj[w].push(v);
    this.edges++;
  }

  /**
   * 打印所有顶点的关系简单表现形式
   * @return {[type]} [description]
   */
  this.showGraph = function() {
    for (var i = 0; i < this.vertices; ++i) {
      var str = i + " ->";
      for (var j = 0; j < this.vertices; ++j) {
        if (this.adj[i][j] != undefined) {
          str += this.adj[i][j] + &#Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb; &#Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb;
        }
      }

      console.log("表现形式为:" + str);
    }

    console.log(this.adj);
  }
}
Copier après la connexion
Recherche en profondeur d'abord


Commencez la visite à partir du nœud initial et marquez-le comme visité . Visitez ensuite récursivement les autres nœuds qui n'ont pas été visités dans la liste de contiguïté du nœud initial. Après cela, vous pouvez visiter tous les nœuds


Selon l'image et le. au-dessus du code, on peut voir que la recherche approfondie peut en fait effectuer de nombreuses autres extensionsRésumé de lexpérience de lorganigramme jsPlumb

Recherche en largeur d'abord
  /**
   * 深度优先搜索算法
   * 这里不需要顶点,也就是邻接表的初始点
   */
    this.dfs = (v) {
        this.marked[v] = true;
        for (var w of this.adj[v]) {
            if (!this.marked[w]) {
                this.dfs(w);
            }
        }
    }
Copier après la connexion

  /**
   * 广度优先搜索算法
   * @param  {[type]} s [description]
   */
  this.bfs = function(s) {
    var queue = [];
    this.marked[s] = true;
    queue.push(s); // 添加到队尾
    while (queue.length > 0) {
      var v = queue.shift(); // 从队首移除
      console.log("Visisted vertex: " + v);
      for (var w of this.adj[v]) {
        if (!this.marked[w]) {
          this.edgeTo[w] = v;
          this.marked[w] = true;
          queue.push(w);
        }
      }
    }
  }
Copier après la connexion

而如果看了《数据结构与算法JavaScript描述》这本书,有兴趣的可以去实现下查找最短路径拓扑排序;

两点之间所有路径

这算是找到的比较能理解的方式来计算
Résumé de lexpérience de lorganigramme jsPlumb

以上图为例,这是一个简单的流程图,可以很简单的看出,右边的流程实际上是未完成的,因为无法到达终点,所以是一个非法点,而通过上面的深度搜索,可以看出,只要对深度优先搜索算法进行一定的修改,那么就可以找到从开始到结束的所有的路径,再通过对比,就可以知道哪些点无法到达终点,从而确定非法点.
上代码:

    /**
     * 深度搜索,dfs,解两点之间所有路径
     * @param  {[type]} v [description]
     * @return {[type]}   [description]
     */
    function Graph2(v) {
        var _this = this;

        this.vertices = v; // 总顶点
        this.edges = 0; //图的起始边数
        this.adj = []; //内部邻接表表现形式
        this.marked = []; // 内部顶点访问状态,与邻接表对应
        this.path = []; // 路径表示
        this.lines = []; // 所有路径汇总

        for (var i = 0; i < this.vertices; ++i) {
            _this.adj[i] = [];
        }

        /**
         * 初始化访问状态
         * @return {[type]} [description]
         */
        this.initMarked = function() {
            for (var i = 0; i < _this.vertices; ++i) {
                _this.marked[i] = false;
            }
        };

        /**
         * 在邻接表中增加节点
         * @param {[type]} v [description]
         * @param {[type]} w [description]
         */
        this.addEdge = function(v, w) {
            this.adj[v].push(w);
            this.edges++;
        };

        /**
         * 返回生成的邻接表
         * @return {[type]} [description]
         */
        this.showGraph = function() {
            return this.adj;
        };

        /**
         * 深度搜索算法
         * @param  {[type]} v    [起点]
         * @param  {[type]} d    [终点]
         * @param  {[type]} path [路径]
         * @return {[type]}      [description]
         */
        this.dfs = function(v, d, path) {
            var _this = this;

            this.marked[v] = true;
            path.push(v);

            if (v == d) {
                var arr = [];
                for (var i = 0; i < path.length; i++) {
                    arr.push(path[i]);
                }

                _this.lines.push(arr);
            } else {
                for (var w of this.adj[v]) {
                    if (!this.marked[w]) {
                        this.dfs(w, d, path);
                    }
                }
            }

            path.pop();
            this.marked[v] = false;
        };

        this.verify = function(arr, start, end) {

            this.initMarked();

            for (var i = 0; i < arr.length; i++) {
                _this.addEdge(arr[i].from, arr[i].to);
            }

            this.dfs(start, end, this.path);
            return this.lines;
        };
    }
Copier après la connexion

可以看出修改了addEdge()函数,将邻接表中的双向记录改为单向记录,可以有效避免下图的错误计算:
Résumé de lexpérience de lorganigramme jsPlumb

只计算起点到终点的所有连线有时并不客观,如果出现
1Résumé de lexpérience de lorganigramme jsPlumb

这种情况的话,实际上深度遍历并不能计算出最右边的节点是合法的,那么就需要重新修改起点和终点,来推导是否能够到达终点.从而判定该点是否合法.至于其他的,只是多了个返回值,存储了一下计算出来的所有路径.
而在dfs函数中,当满足能够从起点走到终点的,则记录下当前的path中的值,保存到lines中去,而每一次对于path的推入或者推出,保证了只有满足条件的点,才能被返回;
this.marked[v] = false,则确保了,在每一次重新计算路径时,都会验证每个点是否存在不同的相对于终点能够到达的路径是否存在.
当然,一定会有更加简单的方法,我这里只是稍微修改了下基础的代码!

redo和undo

这是我觉得最简单却耗时最久的功能,思路都知道:创建一个队列,记录每一次创建一个流程节点,删除一个流程节点,建立一个新的关联关系,删除一个新的关联关系等,都需要记录下来,再通过统一的接口来访问队列,执行操作.
但在具体实现上,jsPlumb的remove确实需要注意一下:
首先,如果需要删除连线,那么使用jsPlumb提供的detach()方法,就可以删除连线,注意,传入的数据应该是connection对象.
当然,也可以使用remove()方法,参数为选择器或者element对象都可以,这个方法删除的是一个节点,包括节点上所有的线.
而jsPlumb中会内部缓存所有的数据,用于刷新,和重连.
那么当我移除一个多层级且内部有连线的情况时,如果只删除最外层的元素,那么内部的连线实际上并没有清除,所以当redo或者移动时,会出现连线的端点有一端会跑到坐标原点,也就是p上(0,0)的地方去.所以清除时,需要注意,要把内部的所有节点依次清除,才不会发生一些莫名其妙的bug.

而在删除和连接连线上,我使用了jsPlumb提供的事件bind(&#Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb;connection&#Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb;)bind("connectionDetached"),用于判断一条连线被连接或者删除.而在记录这里的redo和undo事件时,尤其要注意,需要首先确定删除和连接时的连线的类型,否则会产生额外的队列事件.
因此,在使用连接事件时,就可以使用

jsPlumb.connect({
    source:"foo",
    target:"bar",
    parameters:{
        "p1":Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb,
        "p2":new Date(),
        "pRésumé de lexpérience de lorganigramme jsPlumb":function() { console.log("i am pRésumé de lexpérience de lorganigramme jsPlumb"); }
    }
});
Copier après la connexion

来进行类型的传参,这样事件触发时就可以分类处理.
也可以使用connection.setData()事件,参数可以指定任意的值,通过connection.getData()方法,就可以拿到相应的数据了.
而redo和undo本身确实没有什么东西

    var defaults = {
        &#Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb;name&#Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb;: "mutation",
        &#Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb;afterAddServe&#Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb;:$.noop,
        &#Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb;afterUndo&#Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb;:$.noop,
        &#Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb;afterRedo&#Résumé de lexpérience de lorganigramme jsPlumbRésumé de lexpérience de lorganigramme jsPlumb;:$.noop
    }

    var mutation = function(options){
        this.options = $.extend(true,{},defaults,options);

        this.list = [];
        this.index = 0;
    };

    mutation.prototype = {
        addServe:function(undo,redo){
            if(!_.isFunction(undo) || !_.isFunction(redo)) return false;

            // 说明是在有后续操作时,更新了队列
            if(this.canRedo){
                this.splice(this.index+1);
            };
            this.list.push({
                undo:undo,
                redo:redo
            });

            console.log(this.list);

            this.index = this.list.length - 1;

            _.isFunction(this.options.afterAddServe) && this.options.afterAddServe(this.canUndo(),this.canRedo());
        },
        /**
         * 相当于保存之后清空之前的所有保存的操作
         * @return {[type]} [description]
         */
        reset:function(){
            this.list = [];
            this.index = 0;
        },
        /**
         * 当破坏原来队列时,需要对队列进行修改,
         * index开始的所有存储值都没有用了
         * @param  {[type]} index [description]
         * @return {[type]}       [description]
         */
        splice:function(index){
            this.list.splice(index);
        },
        /**
         * 撤销操作
         * @return {[type]} [description]
         */
        undo:function(){
            if(this.canUndo()){
                this.list[this.index].undo();
                this.index--;

                _.isFunction(this.options.afterUndo) && this.options.afterUndo(this.canUndo(),this.canRedo());
            }
        },
        /**
         * 重做操作
         * @return {[type]} [description]
         */
        redo:function(){
            if(this.canRedo()){
                this.index++;
                this.list[this.index].redo();

                _.isFunction(this.options.afterRedo) && this.options.afterRedo(this.canUndo(),this.canRedo());
            }
        },
        canUndo:function(){
            return this.index !== -1;
        },
        canRedo:function(){
            return this.list.length - 1 !== this.index;
        }
    }

    return mutation;
Copier après la connexion

每次在使用redo或者undo时,只需要判断当前是否是队列的尾端或者起始端,再确定是否redo或者undo就可以了.
调用时的undo()redo()通过传参,将不同的函数封装进队列里,就可以减少耦合度.

放大缩小

这里想了想还是记录一下,方法采用了最简单的mousedownmousemove,让元素在节流中动态的变化大小,就可以了,
1Résumé de lexpérience de lorganigramme jsPlumb

只需要用一个节点,在点击元素时,根据元素的大小来确定该辅助节点四个点的位置,就可以了,只要监听了这四个点的位置,再同步给该定位元素,就能实现这一效果,方法就不贴了,没有太多东西

Résumé

Personnellement, je pense que ce projet est assez intéressant. Je peux apprendre de nouveaux algorithmes et comprendre de nouvelles structures de données, y compris des modèles de conception pour y intégrer le code. et le modèle de publication-abonnement m'a donné une nouvelle compréhension de js. Bien que require ait été utilisé pour gérer les modules, la structure est toujours fortement couplée et devrait toujours être restreinte
En guise de résignation Concernant le dernier projet précédent, je le ressens en fait. que mes capacités de codage n'ont toujours pas beaucoup changé depuis le début de l'année. Il est peut-être temps de rompre avec l'environnement confortable et de recommencer

.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal