// 更新线条动画 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(); }); }); }
在此只贴出部分代码,如果大家有任何建议和问题,还请留言,谢谢。