前端流程圖(DAG)簡單實現(xiàn)(三)[完結(jié)]

沒看過(一)的選手請點我
沒看過(二)的選手請點我

本期內(nèi)容將實現(xiàn)以下操作: 整圖拖動 整圖縮放 全屏操作 橡皮筋選框

配套閱讀: github地址 性感網(wǎng)站在線模擬->點擊step8

ezgif-1-c325e1915bf9.gif

下面放一個工作中使用的較復雜模型


模型示例.png

十一嚣镜、 整圖拖動的實現(xiàn)


graph_drag.png

整圖拖動的實現(xiàn)
把整圖放進svg內(nèi)部的一個g元素內(nèi), 動態(tài)傳入g元素上transfrom的translate進行位置的變換,由于是組件的狀態(tài)值(state),筆者不建議放入vue-x進行管控,建議放入vue組件里的data即可, 在本項目中筆者存入了sessionStorage, 方便后面精確計算當前鼠標位置和原始比例中鼠標的所屬位置.

 svgMouseDown(e) {
      // svg鼠標按下觸發(fā)事件分發(fā)
      this.setInitRect();
      if (this.currentEvent === "sel_area") {
        this.selAreaStart(e);
      } else {
        // 那就拖動畫布
        this.currentEvent = "move_graph";
        this.graphMovePre(e);
      }
    },

事件觸發(fā): 在svg畫布mousedown的時候進行事件分發(fā)

 /**
     * 畫布拖動
     */
    graphMovePre(e) {
      const { x, y } = e;
      this.svg_trans_init = { x, y };
      this.svg_trans_pre = { x: this.svg_left, y: this.svg_top };
    },
    graphMoveIng(e) {
      const { x, y } = this.svg_trans_init;
      this.svg_left = e.x - x + this.svg_trans_pre.x;
      this.svg_top = e.y - y + this.svg_trans_pre.y;
      sessionStorage["svg_left"] = this.svg_left;
      sessionStorage["svg_top"] = this.svg_top;
    },

在mousemove的過程中監(jiān)聽鼠標動態(tài)變化, 通過比較mousedown的初始位置,來更改當前畫布位置
關于坐標計算的問題放在整圖縮放里講, 回歸坐標計算需要考慮縮放倍數(shù)

十二冗澈、 整圖縮放的實現(xiàn) & 當前鼠標位置計算原始坐標

同十一, 通過svg下面g標簽的transform: scale(x), 來進行節(jié)點的整體縮放

    <g :transform="` translate(${svg_left}, ${svg_top}) scale(${svgScale})`" >

在這里svgScale使用了vue-x來管控 , 是想證明, 組件的狀態(tài)管理, 沒有統(tǒng)一規(guī)范, 但是依然強烈建議state交給組件, 數(shù)據(jù)(data)交給vue-x.
↓↓

    svgScale: state => state.dagStore.svgSize

這里新增一個懸浮欄組件, 方便用戶操作. 沒有用icon-font, 直接手打的字符, 后期再美化吧~~

<template>
     <g>
        <foreignObject width="200px" height="30px" style="position: relative">
        <body xmlns="http://www.w3.org/1999/xhtml">
            <div class="control_menu">
                <span @click="sizeExpend">╋</span>
                <span @click="sizeShrink">一</span>
                <span @click="sizeInit">╬</span>
                <span :class="['sel_area', 'sel_area_ing'].indexOf(currentEvent) !== -1 ? 'sel_ing' : ''" @click="sel_area($event)">口</span>
                <span @click="fullScreen">{{ changeScreen }}</span>
            </div>
        </body>
        </foreignObject>
    </g>
</template>
 /**
     *  svg畫板縮放行為
     */
    sizeInit() {
      this.changeSize("init"); // 回歸到默認倍數(shù)
      this.svg_left = 0; // 回歸到默認位置
      this.svg_top = 0;
      sessionStorage['svg_left'] = 0;
      sessionStorage['svg_top'] = 0;
    },
    sizeExpend() {
      this.changeSize("expend"); // 畫板放大0.1
    },
    sizeShrink() {
      this.changeSize("shrink"); // 畫板縮小0.1
    },

由于是vue-x管控,所以在mutation里改變svgSize

CHANGE_SIZE: (state, action) => {
      switch (action) {
        case 'init':
          state.svgSize = 1
          break
        case 'expend':
          state.svgSize += 0.1
          break
        case 'shrink':
          state.svgSize -= 0.1
          break
        default: state.svgSize = state.svgSize
      }
      sessionStorage['svgScale'] = state.svgSize
    },

截至目前, 我們已經(jīng)完成了graph的坐標移動和縮放功能,下面有個重要的問題,就是我們在操作坐標行為的時候,拿到的只能是在組件中的坐標, 這樣會導致所有的結(jié)果都是錯位的,我們需要重新計算,拿回無縮放無位移時的真正坐標.

以節(jié)點拖動結(jié)束為例

paneDragEnd(e) {
      // 節(jié)點拖動結(jié)束
      this.dragFrame = { dragFrame: false, posX: 0, posY: 0 }; // 關閉模態(tài)框
      const x = // x軸坐標需要減去X軸位移量, 再除以放縮比例 減去模態(tài)框?qū)挾纫话?        (e.x - this.initPos.left - (sessionStorage["svg_left"] || 0)) / this.svgScale -
        90;
      const y = // y軸坐標需要減去y軸位移量, 再除以放縮比例 減去模態(tài)框高度一半
        (e.y - this.initPos.top - (sessionStorage["svg_top"] || 0)) / this.svgScale -
        15;
      let params = {
        model_id: sessionStorage["newGraph"],
        id: this.DataAll.nodes[this.choice.index].id,
        pos_x: x,
        pos_y: y
      };
      this.moveNode(params);
    },

所有用得到坐標的位置,都需要減去橫縱坐標偏移量再除以縮放的比例獲取原始比例.代碼不再贅述,可以github down完看step8的內(nèi)容.

十三湃交、全屏
目前只做了chrome瀏覽器的兼容!!!其他沒寫!!!做兼容的查對應瀏覽器API吧

    fullScreen() {
      if (this.changeScreen === "全") {
        this.changeScreen = "關";
        let root = document.getElementById("svgContent");
        root.webkitRequestFullScreen();
      } else {
        this.changeScreen = "全";
        document.webkitExitFullscreen();
      }
    }

document.getElementById('svgContent').webkitRequestFullScreen() 將該元素全屏
document.webkitExitFullScreen() 退出全屏.

十四、橡皮筋選框

橡皮筋選框的思路是, 拖動一個div模態(tài)框,獲取左上和右下的坐標, 改變兩坐標內(nèi)的節(jié)點的選取狀態(tài)即可.

                <div :class="choice.paneNode.indexOf(item.id) !== -1 ? 'pane-node-content selected' : 'pane-node-content'">
      choice: {
        paneNode: [], // 選取的節(jié)點下標組
        index: -1,
        point: -1 // 選取的點數(shù)的下標
      },

選取狀態(tài)為組件的狀態(tài),故放在組件管控,不走vuex. 框選只需要把選擇元素的id push到paneNode里即可.

selAreaStart(e) {
      // 框選節(jié)點開始 在mousedown的時候調(diào)用
      this.currentEvent = "sel_area_ing";
      const x =
        (e.x - this.initPos.left - (sessionStorage["svg_left"] || 0)) /
        this.svgScale;
      const y =
        (e.y - this.initPos.top - (sessionStorage["svg_top"] || 0)) /
        this.svgScale;
      this.simulate_sel_area = {
        left: x,
        top: y,
        width: 0,
        height: 0
      };
    },
    setSelAreaPostion(e) {
      // 框選節(jié)點ing  
      const x =
        (e.x - this.initPos.left - (sessionStorage["svg_left"] || 0)) /
        this.svgScale;
      const y =
        (e.y - this.initPos.top - (sessionStorage["svg_top"] || 0)) /
        this.svgScale;
      const width = x - this.simulate_sel_area.left;
      const height = y - this.simulate_sel_area.top;
      this.simulate_sel_area.width = width;
      this.simulate_sel_area.height = height;
    },
    getSelNodes(postions) {
      // 選取框選的節(jié)點
      const { left, top, width, height } = postions;
      this.choice.paneNode.length = 0;
      this.DataAll.nodes.forEach(item => {
        if (
          item.pos_x > left &&
          item.pos_x < left + width &&
          item.pos_y > top &&
          item.pos_y < top + height
        ) {
          this.choice.paneNode.push(item.id);
        }
      });
      console.log("目前選擇的節(jié)點是", this.choice.paneNode);
    },

this.simulate_sel_area 放置框選模態(tài)框的起點坐標及高寬,傳遞給組件使用即可.

十五咐汞、 事件整理
截至目前,我們項目里充斥著大量的事件,模仿js單線程,通過currentEvent來控制事件行為, 通過監(jiān)聽觸發(fā)對應事件,進行事件分發(fā).

 /**
     * 事件分發(fā)器
     */
    dragPre(e, i, item) {
      // 準備拖動節(jié)點
      this.setInitRect(); // 工具類 初始化dom坐標
      this.currentEvent = "dragPane"; // 修正行為
      this.choice.index = i;
      this.timeStamp = e.timeStamp;
      this.selPaneNode(item.id);
      this.setDragFramePosition(e);
      e.preventDefault();
      e.stopPropagation();
      e.cancelBubble = true;
    },
    dragIng(e) {
      // 事件發(fā)放器 根據(jù)currentEvent來執(zhí)行系列事件
      if (
        this.currentEvent === "dragPane" &&
        e.timeStamp - this.timeStamp > 200 // 拖動節(jié)點延遲200毫秒響應, 來判斷點擊事件
      ) {
        this.currentEvent = "PaneDraging"; // 確認是拖動節(jié)點
      } else if (this.currentEvent === "PaneDraging") {
        this.setDragFramePosition(e); // 觸發(fā)節(jié)點拖動
      } else if (this.currentEvent === "dragLink") {
        this.setDragLinkPostion(e); // 觸發(fā)連線拖動
      } else if (this.currentEvent === "sel_area_ing") {
        this.setSelAreaPostion(e); // 觸發(fā)框選
      } else if (this.currentEvent === "move_graph") {
        this.graphMoveIng(e);
      }
    },
    dragEnd(e) {
      // 拖動結(jié)束
      if (this.currentEvent === "PaneDraging") {
        this.paneDragEnd(e); // 觸發(fā)節(jié)點拖動結(jié)束
      }
      if (this.currentEvent === "sel_area_ing") {
        this.getSelNodes(this.simulate_sel_area);
        this.simulate_sel_area = {
          // 觸發(fā)框選結(jié)束
          left: 0,
          top: 0,
          width: 0,
          height: 0
        };
      }
      this.currentEvent = null;
    },

回顧三期的內(nèi)容, 用了三周的時間完成模型可視化需求的抽離并更新到簡書上, 希望能給有需要的同仁以淺顯的幫助,關于本項目有什么好的想法或者建議,歡迎轉(zhuǎn)到gayhub添加微信.工作之余的時間可以交流.

謝謝每一位看到這里的同學
Thanks?(?ω?)?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拆祈,隨后出現(xiàn)的幾起案子瞳抓,更是在濱河造成了極大的恐慌,老刑警劉巖留特,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纠脾,死亡現(xiàn)場離奇詭異,居然都是意外死亡蜕青,警方通過查閱死者的電腦和手機苟蹈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來右核,“玉大人汉操,你說我怎么就攤上這事∶衫迹” “怎么了磷瘤?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長搜变。 經(jīng)常有香客問我采缚,道長,這世上最難降的妖魔是什么挠他? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任扳抽,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贸呢。我一直安慰自己镰烧,他們只是感情好,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布楞陷。 她就那樣靜靜地躺著怔鳖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪固蛾。 梳的紋絲不亂的頭發(fā)上结执,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機與錄音艾凯,去河邊找鬼献幔。 笑死,一個胖子當著我的面吹牛趾诗,可吹牛的內(nèi)容都是我干的蜡感。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼恃泪,長吁一口氣:“原來是場噩夢啊……” “哼铸敏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起悟泵,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤杈笔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后糕非,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蒙具,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年朽肥,在試婚紗的時候發(fā)現(xiàn)自己被綠了禁筏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡衡招,死狀恐怖篱昔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情始腾,我是刑警寧澤州刽,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站浪箭,受9級特大地震影響穗椅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜奶栖,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一匹表、第九天 我趴在偏房一處隱蔽的房頂上張望门坷。 院中可真熱鬧,春花似錦袍镀、人聲如沸默蚌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绸吸。三九已至,卻和暖如春宣虾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背温数。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工绣硝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人撑刺。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓鹉胖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親够傍。 傳聞我的和親對象是個殘疾皇子甫菠,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容

  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,969評論 3 119
  • 女兒昨晚寫了一封信給我冕屯,雖然寫的字跡不大工整寂诱,我卻看的很感動,她寫了自己成長中對身體的苦惱安聘,這幾年覺得做的錯誤的不...
    遇見一枚魚閱讀 156評論 0 0