麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 編程 > JavaScript > 正文

利用d3.js制作連線動畫圖與編輯器的方法實例

2019-11-19 10:52:57
字體:
來源:轉載
供稿:網友

連線動畫圖

編輯器

效果如上圖所示。

本項目使用主要d3.jsv4制作,分兩部分,一個是實際展示的連線動畫圖,另一個是管理人員使用鼠標編輯連線的頁面。對于d3.js如何引入圖片,如何畫線等基礎功能,這里就不再介紹了,大家可以找一些入門文章看一下。這里主要介紹一下重點問題。

1.連線動畫圖

此圖的主要功能是每隔給定時間,通過ajax請求后臺數據,并根據返回的數據動態改變每個圖片下方的數值,動態改變連線上的動畫流動方向和是否流動。

首先,確定圖表中需要配置的內容,如各圖片存儲位置,連線和動畫顏色,圖片和連線的坐標等。這些數據需要在html中進行配置,最好寫成object對象,賦值給我們自己的圖表類的函數。比如:

var data = { element:[{ image: 'img/work.png', pos:[1,1], // 圖片位置 linePoint:[], // 圖片發出線段坐標數組 lineDir:0, // 線段動畫方向 title: '工作' }], lineColor:'black', // 連線顏色 animateColor: 'red', // 動畫顏色};var chart = new Myd3chart('#chart');chart.lineChart(data);

其中圖片發出的線段坐標數組,使用外部文件提供,此文件由之后介紹的編輯器生成。

在設計我們自己的圖表函數時,最好把每個功能劃分成獨立的函數,這樣方便以后的維護和擴展。

動畫線段采用css的方式,有動畫的線段添加此css即可:

.animate-line{ fill: none; stroke-width: 1; stroke-dasharray: 50 100; stroke-dashoffset: 0; animation: stroke 6s infinite linear;}@keyframes stroke { 100% { stroke-dashoffset: 500; /* 如果反向移動改為-500 */ }}

這個圖表的難點在于動態改變連線上的流動動畫,因為A線段的終點會連接到B線段上,如果B線段動畫停止,則A線段上的動畫仍然要從B上經過,而不能簡單停止B線段上的動畫。而且如果B線段上的接入點不止一個,還要判斷接入點之間的順序,只顯示最靠近B起始點的接入點的動畫。另外還要判斷接入線段上是否有接入線段,層級關系里面如果有1個線段有動畫,則此接入點就有動畫流出。(這里說起來有點繞)

我的方法是:

1)統計每個線段上的所有接入點,這里就是圖片名稱,用于判斷此線段是否有動畫流出。

2)接收后臺傳來的數據時,判斷每個線段是否有動畫,如果有動畫,則直接恢復其動畫線段的起始點坐標;如果沒有動畫,則判斷最靠近起始點的接入點是否有動畫,如果有動畫則將動畫線段的起始點改為此接入點坐標。

// 統計接入點 function findAccessPoint() { var accessPoints = []; // 記錄每個線段上的接入點,data為配置數據 data.eles.forEach(function(d, i){ if(d.line.length == 0){ return; } var acsp = { name: d.title.text, ap: [], // 接入點,按順序排列,頭部離開始點近 }; // 本線段上,每兩相鄰的點作為一個元素存入數組 var linePair = []; // 本線段起始點 var startPos = d.line[0]; d.line.forEach(function(dd, di){ if(d.line[di+1]){  var pair = {  start: dd,  end: d.line[di+1]  };  linePair.push(pair); }  }); // 對每兩相鄰的點,查找接入點 linePair.forEach(function(dd, di){ chartData.eles.forEach(function(ddd, ddi){  // 排除自己,查找自己線段上的接入點  if(i != ddi && ddd.line.length > 1){  // 得到此線段終點  var pos = ddd.line[ddd.line.length - 1];  // dd.start開始點,dd.end結束點  // 用x坐標計算在本線段上的y坐標,再和實際的y坐標比較  var computeY = dd.start[1] +   (pos[0] - dd.start[0])*(dd.end[1] - dd.start[1])/(dd.end[0] - dd.start[0]);  var dif = Math.abs(computeY - pos[1]);  // 如果誤差在2以內,并且此線終點在當前線起點和終點之間  // 認為此點為接入點  if(dif < 2 && (  (  ((pos[0] > dd.start[0]) && (pos[0] < dd.end[0])) ||  ((pos[0] < dd.start[0]) && (pos[0] > dd.end[0]))  ) && (  ((pos[1] > dd.start[1]) && (pos[1] < dd.end[1])) ||  ((pos[1] < dd.start[1]) && (pos[1] > dd.end[1]))  )  )) {  var dis = Math.pow((pos[0] - startPos[0]),2) + Math.pow((pos[1] - startPos[1]),2);  var ap = {  name: ddd.title.text,  ap: pos,  distance: dis, // 距離起始點的距離  allNames: [], // 所有通過此接入點的站點名稱  }  acsp.ap.push(ap);    }  } }); }) accessPoints.push(acsp); }); //對所有的接入點,按與起始點的距離排序,并查找此接入點的上層站點 accessPoints.forEach(function(d, i){ // 按distance由小到大排序 d.ap.sort(function(a, b){ return a.distance - b.distance; }); // 查找每個接入點的上層站點 d.ap.forEach(function(dd, di){ findPoint(dd.name, dd.allNames); }); }); // name是接入點名稱,arr是該接入點的allNames function findPoint(name, arr){ accessPoints.forEach(function(d, i){ // 在數組中找到指定名稱的項 if(d.name === name){  if(d.ap.length>0){  // 把該項下面的ap中的名稱加入給定arr  d.ap.forEach(function(dd, di){  arr.push(dd.name);  // 如果該點內的allNames已經有值則直接加入  if(dd.allNames.length>0){  dd.allNames.forEach(function(d, i){   arr.push(d);  });  } else{  // 遞歸查找子接入點  findPoint(dd.name, arr);  }  });  } else {  return;  } }else{  return; } }); } }

以上函數的運行結果會產生一個對象,存儲每個接入線段上‘掛載'的接入點,目的就是改變動畫時方便判斷。

// 更新線條動畫 aniLine.each(function(d, i){ var curLine = d3.select(this); // 找到對應的動畫line if (dd.name === curLine.attr('tag')) {  // 處理動畫是否運行  if (dd.ani) {  // 此線條動畫運行  curLine.style('animation-play-state', 'running');  curLine.style('display', 'inline');  // 如果動畫運行,則恢復原始動畫路徑  curLine.attr('d', function(d){  return line(chartData.eles[i].line);  });  } else {  // 此線條動畫停止  // 先查找離本線段開始點最近的接入點  var acp = accessPoints;  // 從accessPoints中找到本節點的接入點集合  var ap = [];  acp.forEach(function(acd, aci){  if(acd.name === dd.name){  ap = acd.ap;  }  });    // 最近有動畫接入點序號  var acIndex = -1;  // 找到最近的有動畫接入點,遠近按數組序號遞增  for(var j=0;j<ap.length;j++){  // 復制所有子接入點數組  var allNames = ap[j].allNames.concat();  // 將接入點名稱也加入  allNames.push(ap[j].name);  // 判斷此接入點樹中是否有動畫,如果1個有就可以  allNames.forEach(function(name,ani){  data.forEach(function(datad, datai){   if(datad.name === name){   if(datad.ani){   acIndex = j;   return;   }   }  });  });  if(acIndex != -1) {  break;  }  }  // 如果存在有動畫接入點  if(acIndex != -1){  curLine.style('animation-play-state', 'running');  curLine.style('display', 'inline');  curLine.attr('d', function(d){  var accp = ap[acIndex].ap;  var curLine = data.element[i].line.concat();  // 接入節點與開始點的距離  var disAp = Math.pow((accp[0] - curLine[0][0]),2) +  Math.pow((accp[1] - curLine[0][1]),2);  // 如果當前線段中有離開始節點比接入點近的節點  // 則刪除此節點  curLine.forEach(function(curld, curli){   if(curli > 0){   var dis = Math.pow((curld[0] - curLine[0][0]),2) +   Math.pow((curld[1] - curLine[0][1]),2);   if(dis < disAp){   // 刪除此點   curLine.splice(curli,1);   }   }  });  // 從此接入點處開始動畫  curLine.splice(0,1,accp);  // debugger;  return line(curLine);  });  }else{  // 此線條動畫停止  curLine.style('animation-play-state', 'paused');  curLine.style('display', 'none');  }  } }

2.編輯器

由于本圖表需要配置大量坐標,如果手動填寫的話效率十分低下,所以需要開發一個編輯器用來修改圖表。

編輯器的主要使用方法為,使用鼠標拖動圖標,雙擊確定起始位置并開始實時畫線狀態,隨著鼠標移動動態畫出線段,單擊確定臨時終點,再單擊確定下一個終點,右擊結束動態畫線狀態。如果鼠標單擊其他圖標,則終點為該圖標的起始坐標。本程序的實時畫線部分進行了傾斜的約束,即左傾或右傾30度角。

編輯器比展示圖要簡單一些,復雜部分在事件處理。

// 拖動圖標 var draging = d3.drag() .on('drag', function () { // 當長寬相同時,iconSize是圖標大小[寬,高] var move = iconSize[0] / 2,  moveSubBg = [25, 53.5], moveTitle = [25, 50]; var g = d3.select(this),  eventX = d3.event.x - move,  eventY = d3.event.y - move; // 設定圖標位置 g.select('.image')  .attr('x', eventX)  .attr('y', eventY); }) // 拖拽結束 .on('end', function () { var g = d3.select(this); g.select('.subBg')  .attr('transform', function (d, i) {  // 對子標簽的處理,自動符合字符串長度  var x = parseFloat(d3.select(this).attr('x')) + parseFloat(d3.select(this).attr('width')) / 2,  // y沒被縮放,所以不用處理  y = d3.select(this).attr('y'),  dsl = (d.title.subTitle.text + '').length;  var scaleX = dsl * 5.5;  return 'translate(' + x + ' ' + y + ') scale(' + scaleX + ', 1) translate(' + -x + ' ' + -y + ')';  }); }); // 圖標組增加拖動事件 imageGs.call(draging);

以上拖動事件,只是調用基本方法。

實時畫線功能需要提前定義臨時存儲對象,用來存儲鼠標移動時線段的終點坐標。

// 鼠標移動時,實時畫線到鼠標當前位置,_bodyRect為主區域 _bodyRect.on('mousemove', function(){ // 如果不處于實時畫線狀態 if(!_chartData.drawing){ return; } // 如果沒有端點名稱 if (!_chartData.linePrePare.name) { return; } /* 實時畫線 */ // 判斷線段傾斜方向,linePrePare為線段臨時存儲 var preLines = linePrePare.lines; var mousePos = d3.mouse(_bodyRect.node()), beforePos = preLines[preLines.length - 1], newy, newPos = []; if((mousePos[0]>beforePos[0] && mousePos[1]>beforePos[1]) || (mousePos[0]<beforePos[0] && mousePos[1]<beforePos[1])){ // 向左傾斜/ 左上到右下:y = cy + 0.7*(x-cx) newy = beforePos[1] + 0.7 * (mousePos[0] - beforePos[0]); } else { // 向右傾斜/ 左下到右上:y = cy - 0.7*(cx-x) newy = beforePos[1] - 0.7 * (mousePos[0] - beforePos[0]); } newPos = [mousePos[0], newy]; // 移除舊線 if(_chartData.tempLine.line){ _chartData.tempLine.pos = []; _chartData.tempLine.line.remove(); } // 畫新線,tempLine為實時畫線的臨時存儲 _chartData.tempLine.line = _chartData.lineRootG.append('path') .attr('class', 'line-path') .attr('stroke', chartData.line.color) .attr('stroke-width', chartData.line.width) .attr('fill', 'none') .attr('d', function () {  var newLine = [  preLines[preLines.length - 1],  newPos  ];  _chartData.tempLine.pos = newPos;  return line(newLine); }); // 當鼠標移入某個建筑圖標范圍時 _chartData.imageGs.on('mouseenter', function(d, i){ // 移除舊線 if(_chartData.tempLine.line){  _chartData.tempLine.pos = [];  _chartData.tempLine.line.remove(); } // 得到圖標中心點坐標 var posX = parseFloat(d3.select(this).select('.image').attr('x')) + _chartConf.baseSize[0] / 2; var posY = parseFloat(d3.select(this).select('.image').attr('y')) + _chartConf.baseSize[1] / 2; // 將此建筑圖標的中心點坐標作為終點坐標畫線 _chartData.tempLine.line = _chartData.lineRootG.append('path')  .attr('class', 'line-path')  .attr('stroke', chartData.line.color)  .attr('stroke-width', chartData.line.width)  .attr('fill', 'none')  .attr('d', function () {  var newLine = [  preLines[preLines.length - 1],  [posX,posY]  ];  _chartData.tempLine.pos = [posX,posY];  return line(newLine);  }); }); // 當鼠標移出圖標區域 _chartData.imageGs.on('mouseleave', function(d, i){ // 移除舊線 if(_chartData.tempLine.line){  _chartData.tempLine.pos = [];  _chartData.tempLine.line.remove(); } }); // 對圖標單擊鼠標,保存線 _chartData.imageGs.on('click', function (d, i) { // 保存臨時線 drawLine(); // 停止實時畫線 exitDrawing(); }); }); // 點擊鼠標右鍵,停止實時畫線 _bodyRect.on('contextmenu', function(){ // 停止實時畫線 exitDrawing(); d3.event.preventDefault(); }); }); }

在此只貼出部分代碼,如果大家有任何建議和問題,還請留言,謝謝。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對武林網的支持。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 欧洲精品久久久久69精品 | 国产乱乱视频 | 成人在线免费视频观看 | 一边吃奶一边摸下娇喘 | 欧产日产国产精品v | 91在线看黄 | 国产精品视频自拍 | 黄色网电影 | 在线高清中文字幕 | 一本色道久久综合狠狠躁篇适合什么人看 | 91成人一区二区三区 | 好骚综合在线 | 91久久国产综合久久91猫猫 | 最新黄色电影网站 | 在线小视频国产 | 国产精品久久久久免费视频 | 国产精品99久久久久久董美香 | 日韩视频精品 | 精品国产一区二区三区在线观看 | 日本欧美一区二区三区视频麻豆 | 中文字幕在线观看精品 | 欧洲伊人网 | 久久久av亚洲男天堂 | 欧美三日本三级少妇三级99观看视频 | 日韩欧美电影一区二区三区 | 久久人体 | 黄色成人小视频 | 国产成人精品免费视频大全办公室 | 成人黄色小视频网站 | 爱操成人网 | 精品在线免费播放 | 日韩视频不卡 | 国产精品视频在 | 美女网站黄在线观看 | 中国hdxxxx护士爽在线观看 | 超污视频在线看 | 一区二区三区播放 | 欧美中文字幕一区二区三区亚洲 | 成人精品一区二区 | 91在线视频播放 | 羞羞电影网 |