目次
まえがき
接続時の複数ラベルの処理について
Groups的使用
多层or一层 数据结构解析
redo和undo
放大缩小
まとめ
ホームページ ウェブフロントエンド jsチュートリアル jsPlumb フローチャートの体験概要

jsPlumb フローチャートの体験概要

May 17, 2018 pm 02:03 PM
javascript フローチャート

在使用jsPlumb过程中,所遇到的问题,以及解决方案,文中引用了《数据结构与算法JavaScript描述》的相关图片和一部分代
码.截图是有点多,有时比较懒,没有太多的时间去详细的编辑.
ログイン後にコピー

まえがき

最初にUMLのクラス図
jsPlumb フローチャートの体験概要

次にフローチャート
jsPlumb フローチャートの体験概要

jsPlumbの関連機能を使って、最初のバージョンのプロトタイプを見ることができます 断続的に2か月近くかかりました。途中で中断しました 他のタスクが散在していましたが、それでも基本的な機能は完了しました

実際、作業を終えた後、jsPlumb の機能のほんの一部しか使用されていないことがわかり、理解はさらに深まりました。内部データ構造の実装など、データは同期的に更新されているとしか言えませんが、データドリブンとはまだまだ距離があります

ここではプロジェクトで遭遇した問題点とその解決策をまとめて記録します。もっと良い方法があれば、ご指摘ください

接続時の複数ラベルの処理について

上の図のように、最初は接続中に2つのオーバーレイを設定するかどうか考えましたが、

    var j = jsPlumb.getInstance();

    j.connect({
        source:source,
        target:target,
        overlays:[
            "Arrow",
            ["label",{label:"foo1",location:0.2jsPlumb フローチャートの体験概要,id:"m1"}],
            ["label",{label:"foo2",location:0.jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要,id:"m2"}]
        ]
    })
ログイン後にコピー

のもちろん、ここには落とし穴があります。 ID が重複すると、それが使用されます。 jsPlumb 内にキャッシュされたデータも含めて、最後のものだけが残ります。

後で知ったのですが、設定項目は次のとおりです。 importDefaults 関数を使用して動的に変更することもできます。importDefaults函数来动态修改配置项.

    j.importDefaults({
        ConnectionOverlays: [
            ["Arrow", { location: 1, id: "arrow", length: jsPlumb フローチャートの体験概要, foldback: 0, width: jsPlumb フローチャートの体験概要 }],
            ["Label", { label: "n", id: "label-n", location: 0.2jsPlumb フローチャートの体験概要, cssClass: "jspl-label" }],
            ["Label", { label: "1", id: "label-1", location: 0.jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要, cssClass: "jspl-label" }]
        ]
    })
ログイン後にコピー

只不过这样,只会在运行了函数之后的连线里,才能有两个标签显示,而之前的则无法一起变化.
所以为了方便,直接在初始化里将其给修改了.

Groups的使用

在做流程图时,Group确实是个问题,如上图的无限嵌套层级中,就无法使用jsPlumb提供的Groups功能.
按照文档中来说,如果标识一个元素为组,则该组中的元素则会跟随组的移动而移动,连线也是,但问题就是一旦一个元素成为组了,那就不能接受其它组元素了,换句话说,它所提供的的Groups方法只有一层,自然无法满足要求.
先把总结的组的用法贴出来:

    j.addGroup({
        el:el,
        id:"one"
        constrain:true, // 子元素仅限在元素内拖动
        droppable:true, // 子元素是否可以放置其他元素
        draggable:true, // 默认为true,组是否可以拖动
        dropOverride:true ,// 组中的元素是否可以拓展到其他组,为true时表示否,这里的拓展会对dom结构进行修改,而非单纯的位置移动
        ghost:true, // 是否创建一个子元素的副本元素
        revert:true, // 元素是否可以拖到只有边框可以重合
    })
ログイン後にコピー

后面采用了新的方式,在节点移动时,动态刷新连线

    j.repaintEverything();
ログイン後にコピー

而为了不阻塞页面,需要用到函数节流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 : jsPlumb フローチャートの体験概要00);
        };
    };
ログイン後にコピー

这是一个简单的实现方式,主要就是为了减少dom中事件移动时重复调用的事件,同时达到执行事件的目的(只允许一个函数在x毫秒内执行一次);
当然,也可以使用underscore.js中自带的_.throttle()函数,同样可以达到目的.

这里的html结构就使用了嵌套的层级,将父级和子级使用这种层级保存到内部的数据源里

多层or一层 数据结构解析

jsPlumb フローチャートの体験概要

类似这种实际存在嵌套关系的数据体,有两种方式可以进行管理,

  • 多层级嵌套:类似

        [
            {
                id:"1",
                child:{
                    id:"2",
                    child:{
                        id:"jsPlumb フローチャートの体験概要",
                        child:{}
                    }
                }
            }
        ]
    ログイン後にコピー

    用来进行管理的话,优点是直观,能根据层级就知道整体结构大概是多少,转换成xml或者html也很方便.
    但缺点就是进行查找和修改,并不是那么方便.

  • 一层展示所有节点:类似

        [
            {
                id:"1",
                child:[{
                    id:"2"
                }]
            },
            {
                id:"2",
                parentId:"1",
                child:[{
                    id:"jsPlumb フローチャートの体験概要"
                }]
            },
            {
                id:"jsPlumb フローチャートの体験概要",
                parentId:"2",
                child:[]
            }
        ]
    ログイン後にコピー

    这种结构好处就是全部在一个层级中,查找起来和修改数据非常方便,而如果想要解析成多层级的结构,只需要运用递归,来生成新结构:

    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;
        }
    }
    ログイン後にコピー

    将一层的数组通过初始化函数init

    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] + &#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要; &#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;
            }
          }
    
          console.log("表现形式为:" + str);
        }
    
        console.log(this.adj);
      }
    }
    ログイン後にコピー

    ただし、関数を実行した後の接続でのみ 2 つのラベルが表示され、前のラベルを一緒に変更することはできません。したがって、便宜上、初期化時に直接変更されます。 jsPlumb フローチャートの体験概要

    Group を使用する場合、上記のような無限のネスト レベルでは、Groups を使用できません。 jsPlumb が提供する関数
  • ドキュメントによると、要素をグループとしてマークすると、グループ内の要素はグループの移動に合わせて移動し、接続も移動します。しかし問題は、要素がグループになることです。 、他のグループ要素を受け入れることはできません。 つまり、それが提供するもの Groups メソッドには 1 つのレイヤーしかないため、当然ながら要件を満たすことができません。
まず、グループの使用方法をまとめて投稿します。ノードが移動すると、接続が動的に更新されます

  /**
   * 深度优先搜索算法
   * 这里不需要顶点,也就是邻接表的初始点
   */
    this.dfs = (v) {
        this.marked[v] = true;
        for (var w of this.adj[v]) {
            if (!this.marked[w]) {
                this.dfs(w);
            }
        }
    }
ログイン後にコピー

ページをブロックしないようにするには、関数スロットリング throttle() を使用する必要があります

  /**
   * 广度优先搜索算法
   * @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);
        }
      }
    }
  }
ログイン後にコピー
ログイン後にコピー

これは、主に次のことを行うための簡単な実装方法です。 dom 内のイベントを移動するときに繰り返し呼び出されるイベントの数を減らし、同時にイベントを実行するという目的を達成します (関数は 1 回だけ実行されます) ネストされたレベルは、親レベルと子レベルを内部レベルに保存するために使用されますデータ ソース


多層または 1 層のデータ構造分析jsPlumb フローチャートの体験概要

 jsPlumb フローチャートの体験概要


実際にネストされた関係を持つこのようなデータ本体を管理するには、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;
            };
        }
    ログイン後にコピー
    ログイン後にコピー

    と同様、管理に使う場合は、レベルに応じて大まかな全体構造が分かるのがメリットで、XMLやHTMLに変換するのも非常に便利です

    が、デメリットはあまり便利ではないということです。検索と変更


  • すべてのノードを 1 つのレイヤーに表示します:

    jsPlumb.connect({
        source:"foo",
        target:"bar",
        parameters:{
            "p1":jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要,
            "p2":new Date(),
            "pjsPlumb フローチャートの体験概要":function() { console.log("i am pjsPlumb フローチャートの体験概要"); }
        }
    });
    ログイン後にコピー
    ログイン後にコピー
    に似ています。この構造の利点は、すべてが 1 つのレベルにあることであり、必要に応じてデータを検索および変更するのに非常に便利です。それをマルチレベル構造に解析するには、再帰を使用して新しい構造を生成するだけです:
        var defaults = {
            &#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;name&#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;: "mutation",
            &#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;afterAddServe&#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;:$.noop,
            &#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;afterUndo&#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;:$.noop,
            &#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;afterRedo&#jsPlumb フローチャートの体験概要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;
    ログイン後にコピー
    ログイン後にコピー

    1 レベルの配列を初期化関数 init に通し、マルチレベルの配列に変換します。レベル 1
    jsPlumb フローチャートの体験概要

    HTML 構造に変換したい場合は、関数を少し変更するだけで実現できます

    プロセスに行き止まりがあるかどうか (ポイントがあるかどうか) を確認します。グラフの終端に到達できないパス)jsPlumb フローチャートの体験概要

    これを実現するには、まず、絵の理解が重要なポイントです🎜🎜🎜🎜文字を打つのが面倒なので、表現します。基本的な図はおおよそ次のとおりで、具体的な式は 🎜🎜🎜 ご覧のとおり、基本的なグラフ表現は隣接リストで表すことができます 🎜🎜 そして実装については、次のコードをご覧ください。 : 🎜rrreee🎜そして、構築だけでは十分ではないので、基本的な検索方法を見てみましょう: 🎜深さ優先検索と幅優先検索🎜🎜深さ優先検索🎜🎜まず最初のノードから訪問を開始し、訪問済みとしてマークします。次に、最初のノードの隣接リストにある他の未訪問のノードを再帰的に訪問し、その後、すべてのノードを訪問できます🎜🎜🎜rrreee🎜 画像と上記のコードによると、ディープサーチは実際に他の多くのことを実行できることがわかります。拡張機能🎜🎜幅優先検索🎜🎜🎜🎜
      /**
       * 广度优先搜索算法
       * @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);
            }
          }
        }
      }
    ログイン後にコピー
    ログイン後にコピー

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

    两点之间所有路径

    这算是找到的比较能理解的方式来计算
    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;
            };
        }
    ログイン後にコピー
    ログイン後にコピー

    可以看出修改了addEdge()函数,将邻接表中的双向记录改为单向记录,可以有效避免下图的错误计算:
    jsPlumb フローチャートの体験概要

    只计算起点到终点的所有连线有时并不客观,如果出现
    1jsPlumb フローチャートの体験概要

    这种情况的话,实际上深度遍历并不能计算出最右边的节点是合法的,那么就需要重新修改起点和终点,来推导是否能够到达终点.从而判定该点是否合法.至于其他的,只是多了个返回值,存储了一下计算出来的所有路径.
    而在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(&#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;connection&#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;)bind("connectionDetached"),用于判断一条连线被连接或者删除.而在记录这里的redo和undo事件时,尤其要注意,需要首先确定删除和连接时的连线的类型,否则会产生额外的队列事件.
    因此,在使用连接事件时,就可以使用

    jsPlumb.connect({
        source:"foo",
        target:"bar",
        parameters:{
            "p1":jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要,
            "p2":new Date(),
            "pjsPlumb フローチャートの体験概要":function() { console.log("i am pjsPlumb フローチャートの体験概要"); }
        }
    });
    ログイン後にコピー
    ログイン後にコピー

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

        var defaults = {
            &#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;name&#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;: "mutation",
            &#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;afterAddServe&#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;:$.noop,
            &#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;afterUndo&#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;:$.noop,
            &#jsPlumb フローチャートの体験概要jsPlumb フローチャートの体験概要;afterRedo&#jsPlumb フローチャートの体験概要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;
    ログイン後にコピー
    ログイン後にコピー

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

    放大缩小

    这里想了想还是记录一下,方法采用了最简单的mousedownmousemove,让元素在节流中动态的变化大小,就可以了,
    1jsPlumb フローチャートの体験概要

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

    まとめ

    私は個人的に、このプロジェクトが非常に興味深いと感じています。新しいアルゴリズムを学び、デザイン パターンを含む新しいデータ構造を理解し、それらを統合して使用されるコード、ミドルウェア パターン、リリースを統合することもできます。 js を理解するために、require を使用してモジュールを管理していますが、構造はまだ高度に結合されているため、制限が必要です。実際、退職前の最後のプロジェクトとして、私のコーディング能力は以前とあまり変わっていないように感じます。年の初めは、快適な環境を離れてやり直す時期かもしれません。

    以上がjsPlumb フローチャートの体験概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

WPSドキュメントでフローチャートを描画する方法 WPSドキュメントでフローチャートを描画する方法 Mar 20, 2024 pm 10:20 PM

仕事の会議ではフローチャートを使うことが多いのですが、フローチャートを使うと説明がより直感的で便利になります。実はフローチャートを作るのは難しいことではなく、wpsドキュメントを作るだけで済みます。以下のエディタでは、WPS ドキュメントにフローチャートを描画する具体的な手順を説明します。 1. WPS ソフトウェアを開き、メニュー バーの [挿入] ボタンを選択します。 2. フローチャートの角丸長方形の枠を選択し、ページ上に描画します。中にはさまざまなフローチャートや矢印のパターンがあり、それらを選択して文書内に引き出し、右クリックで文字を追加するだけで文字を入力できます。 3. このとき、描画された長方形の枠が塗りつぶされていることがわかりましたので、描画ツールでリセットして透明色で塗りつぶします。 4. この長方形のボックスをクリックしてマウスを使用してみましょう

WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 Dec 17, 2023 pm 02:54 PM

WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 はじめに: 技術の継続的な発展により、音声認識技術は人工知能の分野の重要な部分になりました。 WebSocket と JavaScript をベースとしたオンライン音声認識システムは、低遅延、リアルタイム、クロスプラットフォームという特徴があり、広く使用されるソリューションとなっています。この記事では、WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法を紹介します。

WebSocket と JavaScript: リアルタイム監視システムを実装するための主要テクノロジー WebSocket と JavaScript: リアルタイム監視システムを実装するための主要テクノロジー Dec 17, 2023 pm 05:30 PM

WebSocketとJavaScript:リアルタイム監視システムを実現するためのキーテクノロジー はじめに: インターネット技術の急速な発展に伴い、リアルタイム監視システムは様々な分野で広く利用されています。リアルタイム監視を実現するための重要なテクノロジーの 1 つは、WebSocket と JavaScript の組み合わせです。この記事では、リアルタイム監視システムにおける WebSocket と JavaScript のアプリケーションを紹介し、コード例を示し、その実装原理を詳しく説明します。 1.WebSocketテクノロジー

JavaScript と WebSocket を使用してリアルタイムのオンライン注文システムを実装する方法 JavaScript と WebSocket を使用してリアルタイムのオンライン注文システムを実装する方法 Dec 17, 2023 pm 12:09 PM

JavaScript と WebSocket を使用してリアルタイム オンライン注文システムを実装する方法の紹介: インターネットの普及とテクノロジーの進歩に伴い、ますます多くのレストランがオンライン注文サービスを提供し始めています。リアルタイムのオンライン注文システムを実装するには、JavaScript と WebSocket テクノロジを使用できます。 WebSocket は、TCP プロトコルをベースとした全二重通信プロトコルで、クライアントとサーバー間のリアルタイム双方向通信を実現します。リアルタイムオンラインオーダーシステムにおいて、ユーザーが料理を選択して注文するとき

WebSocketとJavaScriptを使ったオンライン予約システムの実装方法 WebSocketとJavaScriptを使ったオンライン予約システムの実装方法 Dec 17, 2023 am 09:39 AM

WebSocket と JavaScript を使用してオンライン予約システムを実装する方法 今日のデジタル時代では、ますます多くの企業やサービスがオンライン予約機能を提供する必要があります。効率的かつリアルタイムのオンライン予約システムを実装することが重要です。この記事では、WebSocket と JavaScript を使用してオンライン予約システムを実装する方法と、具体的なコード例を紹介します。 1. WebSocket とは何ですか? WebSocket は、単一の TCP 接続における全二重方式です。

JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 Dec 17, 2023 pm 05:13 PM

JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 はじめに: 今日、天気予報の精度は日常生活と意思決定にとって非常に重要です。テクノロジーの発展に伴い、リアルタイムで気象データを取得することで、より正確で信頼性の高い天気予報を提供できるようになりました。この記事では、JavaScript と WebSocket テクノロジを使用して効率的なリアルタイム天気予報システムを構築する方法を学びます。この記事では、具体的なコード例を通じて実装プロセスを説明します。私たちは

簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 Jan 05, 2024 pm 06:08 PM

JavaScript チュートリアル: HTTP ステータス コードを取得する方法、特定のコード例が必要です 序文: Web 開発では、サーバーとのデータ対話が頻繁に発生します。サーバーと通信するとき、多くの場合、返された HTTP ステータス コードを取得して操作が成功したかどうかを判断し、さまざまなステータス コードに基づいて対応する処理を実行する必要があります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法を説明し、いくつかの実用的なコード例を示します。 XMLHttpRequestの使用

JavaScriptでinsertBeforeを使用する方法 JavaScriptでinsertBeforeを使用する方法 Nov 24, 2023 am 11:56 AM

使用法: JavaScript では、insertBefore() メソッドを使用して、DOM ツリーに新しいノードを挿入します。このメソッドには、挿入される新しいノードと参照ノード (つまり、新しいノードが挿入されるノード) の 2 つのパラメータが必要です。

See all articles