最近在做一个需求,根据数据动态生成以下类似的流程图,需要可以设置每个节点的颜色,每个节点可添加点击移动等相关的事件

代码中有做很多的注释和说明,相关的文档说明链接:https://9eb75i.axshare.com

drawFlowChart.js

  1. //画流程图
  2. //画所有的图形:画图和画对应的箭头
  3. function drawFlowChart(context,canvas,flowdata,initTop, initSpaceH){
  4. //1、判断是否有需要平均计算x的数据
  5. flowdata.forEach(function(row){
  6. if(row.isAverage){
  7. row.data = calChartX(canvas.width,row.data, row.y);
  8. }
  9. });
  10. //2、先要画完所有的图形
  11. flowdata.forEach(function(row,rowIndex){
  12. row.y = row.y ? row.y : ( rowIndex==0 ? initTop + initSpaceH : initTop + initSpaceH*rowIndex);
  13. row = drawRowChart(context, row); //画图形
  14. });
  15. //3、添加要指向的对象,必须要在画完所有图形之后
  16. flowdata.forEach(function(row){
  17. row.data.forEach(function(item){
  18. if(item.arrowArr && item.arrowArr.length){
  19. item.arrowArr.forEach(function(mItem){
  20. mItem = addToObj(mItem,flowdata);
  21. })
  22. }
  23. })
  24. });
  25. //4、给所有图形画上对应的画箭头,必须要在前两步完成之后
  26. flowdata.forEach(function(row,rowIndex){
  27. row.data.forEach(function(item){
  28. if(item.arrowArr && item.arrowArr.length){
  29. drawSingleArrow(context,item);//画箭头
  30. }
  31. })
  32. });
  33. //5、给所有元素添加点击和悬浮事件
  34. addMethod(canvas,flowdata)
  35. }
  36. //当一行有n个图形并且需要平均排列时用此方法计算每个图形的x
  37. function calChartX(canvasW,data, dataY){
  38. var startW = 80;
  39. var stepW = 120;
  40. var CondW = 30;
  41. var count = 0;
  42. for(var i=0;i<data.length;i++){
  43. if(data[i].type == 'Step'){
  44. count += stepW;
  45. }else if(data[i].type == 'Start' || data[i].type == 'End'){
  46. count += startW;
  47. }else if(data[i].type == 'Condition'){
  48. count += CondW;
  49. }
  50. }
  51. //spaceW 计算一行中每个图形的平均间距
  52. var spaceW = parseInt((canvasW - count)/(data.length+1));
  53. //计算坐标x
  54. var prevDiv = [], curW = 0;
  55. for(var i=0;i<data.length;i++){
  56. if(data[i].type == 'Step'){
  57. prevDiv.push(stepW);
  58. curW = stepW/2;
  59. }else if(data[i].type == 'Start' || data[i].type == 'End'){
  60. prevDiv.push(startW);
  61. curW = startW/2;
  62. }else if(data[i].type == 'Condition'){
  63. prevDiv.push(CondW);
  64. curW = CondW/2;
  65. }
  66. var preLength = 0;
  67. for(var j=0;j<i;j++){
  68. preLength += prevDiv[j];
  69. }
  70. var x = spaceW*(i+1)+preLength+curW;
  71. var y = data[i].y;
  72. data[i]['x'] = x;
  73. data[i]['y'] = y ? y : dataY;
  74. }
  75. return data;
  76. }
  77. //生成每列对应的图形
  78. function drawRowChart(context, row){
  79. row.data.forEach(function(item,index){
  80. var s = null;
  81. item.y = item.y ? item.y : row.y;
  82. if(item.type == 'Step'){
  83. s = new Step(context,item.x,item.y,item);
  84. }else if(item.type == 'Condition'){
  85. s = new Condition(context,item.x,item.y,item);
  86. }else if(item.type == 'End'){
  87. s = new End(context,item.x,item.y,item);
  88. }else if(item.type == 'Start'){
  89. s = new Start(context,item.x,item.y,item);
  90. }
  91. item.chartObj = s;
  92. })
  93. return row;
  94. }
  95. //绘制单个的图形
  96. function drawSingleChart(context,item){
  97. var s = '';
  98. if(item.type == 'Step'){
  99. s = new Step(context,item.x,item.y,item);
  100. }else if(item.type == 'Condition'){
  101. s = new Condition(context,item.x,item.y,item);
  102. }else if(item.type == 'End'){
  103. s = new End(context,item.x,item.y,item);
  104. }else if(item.type == 'Start'){
  105. s = new Start(context,item.x,item.y,item);
  106. }
  107. item.chartObj = s;
  108. return item;
  109. }
  110. //每个对象的坐标范围
  111. function calRange(obj){
  112. var newObj = {
  113. minX:obj.x-obj.w/2,
  114. maxX:obj.x+obj.w/2,
  115. minY:obj.y-obj.h/2,
  116. maxY:obj.y+obj.h/2
  117. }
  118. return newObj;
  119. }
  120. //处理每一个箭头需要指向的对象
  121. function addToObj(arrObj,flowData){
  122. flowData.forEach(function(rows){
  123. rows.data.forEach(function(item){
  124. if(item.name == arrObj.to){
  125. arrObj.to = item.chartObj;
  126. }
  127. })
  128. })
  129. return arrObj;
  130. }
  131. //话每个图形的箭头指向
  132. function drawSingleArrow(context,data){
  133. var step1 = data.chartObj;
  134. if(data.arrowArr && data.arrowArr.length){
  135. data.arrowArr.forEach(function(item){
  136. step1[item.arrow](item.to,context);
  137. })
  138. }
  139. }
  140. //清除单个图形
  141. function repaintSingleChart(context,item){
  142. var range = item.chartObj.range;
  143. //清除之前画的图形
  144. context.clearRect(range.minX-1,range.minY-1,item.chartObj.w+2,item.chartObj.h+3);
  145. }
  146. //给所有图形添加事件
  147. function addMethod(canvas,flowData){
  148. //给所有图形添加点击事件
  149. canvas.onclick=function(ev){
  150. var ev = ev || window.event;
  151. var ua = navigator.userAgent.toLowerCase();
  152. var isIE = ua.indexOf("compatible") > -1 && ua.indexOf("msie") > -1 && !ua.indexOf("opera") > -1; //IE浏览器
  153. var isEdge= ua.indexOf("Edge") > -1;
  154. var isIE11 = ua.toLowerCase().match(/rv:([\d.]+)\) like gecko/);
  155. var curx = (isIE || isEdge || isIE11) ? ev.offsetX : ev.layerX;
  156. var cury = (isIE || isEdge || isIE11) ? ev.offsetY : ev.layerY;
  157. flowData.forEach(function(row,listIndex){
  158. row.data.forEach(function(item){
  159. var range = item.chartObj.range;
  160. if(curx>=range.minX && curx<=range.maxX && cury>=range.minY && cury<=range.maxY){
  161. var clickMethod = null;
  162. if(row.method && row.method.onclick){ //如果每行定义了事件
  163. //判断每个元素是否有单独定义事件,如果有,取改元素定义的事件,如果没有取每行定义的事件
  164. if(item.method && item.method.onclick){
  165. clickMethod = item.method.onclick
  166. }else{
  167. clickMethod = row.method.onclick
  168. }
  169. }else{
  170. if(item.method && item.method.onclick){
  171. clickMethod = item.method.onclick
  172. }else{
  173. clickMethod = null;
  174. }
  175. }
  176. if(clickMethod instanceof Function){
  177. clickMethod(item);
  178. }
  179. }
  180. })
  181. });
  182. };
  183. var timer = null;
  184. //给所有图形添加mousemove事件
  185. canvas.onmousemove=function(ev){
  186. var ev = ev || window.event;
  187. var ua = navigator.userAgent.toLowerCase();
  188. var isIE = ua.indexOf("compatible") > -1 && ua.indexOf("msie") > -1 && !ua.indexOf("opera") > -1; //IE浏览器
  189. var isEdge= ua.indexOf("Edge") > -1;
  190. var isIE11 = ua.toLowerCase().match(/rv:([\d.]+)\) like gecko/);
  191. var curx = (isIE || isEdge || isIE11) ? ev.offsetX : ev.layerX;
  192. var cury = (isIE || isEdge || isIE11) ? ev.offsetY : ev.layerY;
  193. clearTimeout(timer);
  194. flowData.forEach(function(row,listIndex){
  195. row.data.forEach(function(item){
  196. var range = item.chartObj.range;
  197. if(curx>=range.minX && curx<=range.maxX && cury>=range.minY && cury<=range.maxY){
  198. var clickMethod = null;
  199. if(row.method && row.method.onmousemove){ //如果每行定义了事件
  200. //判断每个元素是否有单独定义事件,如果有,取改元素定义的事件,如果没有取每行定义的事件
  201. if(item.method && item.method.onmousemove){
  202. clickMethod = item.method.onmousemove
  203. }else{
  204. clickMethod = row.method.onmousemove
  205. }
  206. }else{
  207. if(item.method && item.method.onmousemove){
  208. clickMethod = item.method.onmousemove
  209. }else{
  210. clickMethod = null;
  211. }
  212. }
  213. if(clickMethod instanceof Function){
  214. timer = setTimeout(function(){
  215. clickMethod(item);
  216. },1000)
  217. }
  218. }
  219. })
  220. });
  221. }
  222. //给所有图形添加mouseleave事件
  223. canvas.onmouseleave=function(ev){
  224. var ev = ev || window.event;
  225. var ua = navigator.userAgent.toLowerCase();
  226. var isIE = ua.indexOf("compatible") > -1 && ua.indexOf("msie") > -1 && !ua.indexOf("opera") > -1; //IE浏览器
  227. var isEdge= ua.indexOf("Edge") > -1;
  228. var isIE11 = ua.toLowerCase().match(/rv:([\d.]+)\) like gecko/);
  229. var curx = (isIE || isEdge || isIE11) ? ev.offsetX : ev.layerX;
  230. var cury = (isIE || isEdge || isIE11) ? ev.offsetY : ev.layerY;
  231. clearTimeout(timer);
  232. flowData.forEach(function(row){
  233. row.data.forEach(function(item){
  234. var range = item.chartObj.range;
  235. if(curx>=range.minX && curx<=range.maxX && cury>=range.minY && cury<=range.maxY){
  236. var clickMethod = null;
  237. if(row.method && row.method.onmouseleave){ //如果每行定义了事件
  238. //判断每个元素是否有单独定义事件,如果有,取改元素定义的事件,如果没有取每行定义的事件
  239. if(item.method && item.method.onmouseleave){
  240. clickMethod = item.method.onmouseleave
  241. }else{
  242. clickMethod = row.method.onmouseleave
  243. }
  244. }else{
  245. if(item.method && item.method.onmouseleave){
  246. clickMethod = item.method.onmouseleave
  247. }else{
  248. clickMethod = null;
  249. }
  250. }
  251. if(clickMethod instanceof Function){
  252. clickMethod(item);
  253. }
  254. }
  255. })
  256. });
  257. }
  258. }
  259. /////////////////////////////////////////基本画图形start////////////////////////////////////////////////////////
  260. //画圆角矩形
  261. function drawRoundRect(context, x, y, w, h, item, radius) {
  262. radius = radius || 20;
  263. context.beginPath();
  264. context.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2);
  265. context.lineTo(w - radius + x, y);
  266. context.arc(w - radius + x, radius + y, radius, Math.PI * 3 / 2, Math.PI * 2);
  267. context.lineTo(w + x, h + y - radius);
  268. context.arc(w - radius + x, h - radius + y, radius, 0, Math.PI * 1 / 2);
  269. context.lineTo(radius + x, h +y);
  270. context.arc(radius + x, h - radius + y, radius, Math.PI * 1 / 2, Math.PI);
  271. context.closePath();
  272. context.fillStyle = item.color ? (item.color.bgColor ? item.color.bgColor: 'rgba(91,155,213,0.5)'):'white' ; //背景颜色
  273. context.fill();
  274. context.strokeStyle= item.color ? (item.color.borderColor ? item.color.borderColor: '#5B9BD5'):'#5B9BD5' ; //边框颜色
  275. context.font = 'normal 14px 微软雅黑';
  276. context.fillStyle = item.color ? (item.color.fontColor ? item.color.fontColor: '#5B9BD5'):'#5B9BD5' ; //文字颜色
  277. context.textAlign="center";
  278. context.fillText(item.text, x+w/2, y+h/2+6);
  279. context.stroke();
  280. }
  281. //画菱形
  282. function drawRhombus(context,x, y, l, h, item) {
  283. context.beginPath();
  284. context.moveTo(x, y + h);
  285. context.lineTo(x - l * 2-40, y);
  286. context.lineTo(x, y - h);
  287. context.lineTo(x + l * 2+40, y);
  288. context.closePath();
  289. context.strokeStyle= item.color ? (item.color.borderColor ? item.color.borderColor: '#5B9BD5'):'#5B9BD5' ; //边框颜色
  290. context.fillStyle = item.color ? (item.color.bgColor ? item.color.bgColor: 'rgba(91,155,213,0.5)'):'white' ; //背景颜色
  291. context.fill();
  292. context.fillStyle = item.color ? (item.color.fontColor ? item.color.fontColor: '#5B9BD5'):'#5B9BD5' ; //文字颜色
  293. context.font = 'normal 14px 微软雅黑';
  294. context.fillText(item.text, x, y+3);
  295. context.stroke();
  296. }
  297. //计算文本的宽高
  298. function textSize(fontSize,fontFamily,text){
  299. var span = document.createElement("span");
  300. var result = {};
  301. result.width = span.offsetWidth;
  302. result.height = span.offsetHeight;
  303. span.style.visibility = "hidden";
  304. span.style.fontSize = fontSize;
  305. span.style.fontFamily = fontFamily;
  306. span.style.display = "inline-block";
  307. document.body.appendChild(span);
  308. if(typeof span.textContent != "undefined"){
  309. span.textContent = text;
  310. }else{
  311. span.innerText = text;
  312. }
  313. result.width = parseFloat(window.getComputedStyle(span).width) - result.width;
  314. result.height = parseFloat(window.getComputedStyle(span).height) - result.height;
  315. return result;
  316. }
  317. //Start 圆角矩形对象
  318. function Start(context,x, y, item, h, w) {
  319. this.flag = 'start';
  320. var textStyle = textSize('14px','微软雅黑',item.text);
  321. w = parseInt(textStyle.width)+30;
  322. this.h = h || 40;
  323. this.w = w || 2 * this.h;
  324. this.x = x;
  325. this.y = y;
  326. this.text = item.text;
  327. this.range = calRange(this);
  328. drawRoundRect(context,x - this.w / 2, y - this.h / 2, this.w, this.h, item);
  329. }
  330. //End 圆角矩形对象
  331. function End(context, x, y, item, h, w) {
  332. this.flag = 'end';
  333. var textStyle = textSize('14px','微软雅黑',item.text);
  334. w = parseInt(textStyle.width)+30;
  335. this.h = h || 40;
  336. this.w = w || 2 * this.h;
  337. this.x = x;
  338. this.y = y;
  339. this.text = item.text;
  340. this.range = calRange(this);
  341. drawRoundRect(context, x - this.w / 2, y - this.h / 2, this.w, this.h, item, 20);
  342. }
  343. //Step 矩形对象
  344. function Step(context,x, y, item) {
  345. this.flag = "step";
  346. var textStyle = textSize('14px','微软雅黑',item.text);
  347. var w = parseInt(textStyle.width)+30;
  348. this.h = 40;
  349. this.w = w;
  350. this.x = x;
  351. this.y = y;
  352. this.text = item.text;
  353. this.range = calRange(this);
  354. context.strokeStyle= item.color ? (item.color.borderColor ? item.color.borderColor: '#5B9BD5'):'#5B9BD5' ; //边框颜色
  355. context.strokeRect(x - this.w / 2, y - this.h / 2, this.w, this.h);
  356. context.fillStyle = item.color ? (item.color.bgColor ? item.color.bgColor: 'rgba(91,155,213,0.5)'):'white' ; //背景颜色
  357. context.fillRect(x - this.w / 2, y - this.h / 2,this.w,this.h);
  358. context.fillStyle = item.color ? (item.color.fontColor ? item.color.fontColor: '#5B9BD5'):'#5B9BD5' ; //文字颜色
  359. context.textAlign = 'center';
  360. context.font = 'normal 14px 微软雅黑';
  361. if(item.text){context.fillText(item.text,x, y+4);}
  362. }
  363. //Condition 菱形对象
  364. function Condition(context, x, y, item) {
  365. this.flag = "condition";
  366. var textStyle = textSize('14px','微软雅黑',item.text);
  367. var w = parseInt(textStyle.width)/4;
  368. this.l = w;
  369. this.h = 40;
  370. this.w = w;
  371. this.x = x;
  372. this.y = y;
  373. this.text = item.text;
  374. this.range = calRange(this);
  375. drawRhombus(context,x, y, this.l,this.h, item);
  376. }
  377. /////////////////////////////////////////基本画图形end////////////////////////////////////////////////////////
  378. ////////////////////////////////////////////画箭头/////////////////////////////////////////////////////////////
  379. //箭头从start圆角矩形bottom——>top
  380. Start.prototype.drawBottomToTop = function(obj,context) {
  381. if(obj.flag == "step") {
  382. var arrow = new Arrow(this.x, this.y + this.h / 2, obj.x, obj.y - obj.h / 2);
  383. arrow.drawBottomToTop(context);
  384. } else if(obj.flag == "condition") {
  385. var arrow = new Arrow(this.x, this.y + this.h / 2, obj.x, obj.y - obj.l);
  386. arrow.drawBottomToTop(context);
  387. }
  388. }
  389. //箭头从step矩形bottom——>right
  390. Step.prototype.drawBottomToRight = function(obj,context) {
  391. var arrow = null;
  392. if(obj.flag == "step") {
  393. arrow = new Arrow(this.x, this.y + this.h / 2, obj.x + obj.w / 2 , obj.y);
  394. } else if(obj.flag == "condition") {
  395. arrow = new Arrow(this.x , this.y + this.h / 2, obj.x + obj.l*2+40 , obj.y);
  396. }else if(obj.flag == "start" || obj.flag == "end"){
  397. arrow = new Arrow(this.x , this.y + this.h / 2, obj.x + obj.w/2 , obj.y);
  398. }
  399. arrow.drawBottomToRight(context);
  400. }
  401. //箭头从step矩形bottom——>left
  402. Step.prototype.drawBottomToLeft = function(obj,context) {
  403. var arrow = null;
  404. if(obj.flag == "step") {
  405. arrow = new Arrow(this.x, this.y + this.h / 2, obj.x - obj.w / 2 , obj.y);
  406. } else if(obj.flag == "condition") {
  407. arrow = new Arrow(this.x , this.y + this.h / 2, obj.x - obj.l*2-40 , obj.y);
  408. }else if(obj.flag == "start" || obj.flag == "end"){
  409. arrow = new Arrow(this.x , this.y + this.h / 2, obj.x - obj.w/2 , obj.y);
  410. }
  411. arrow.drawBottomToRight(context);
  412. }
  413. //箭头从step矩形bottom——>top
  414. Step.prototype.drawBottomToTop = function(obj,context) {
  415. if(obj.flag == "step") {
  416. var arrow = new Arrow(this.x, this.y + this.h / 2, obj.x, obj.y - obj.h / 2);
  417. arrow.drawBottomToTop(context);
  418. } else if(obj.flag == "condition") {
  419. var arrow = new Arrow(this.x, this.y + this.h / 2, obj.x, obj.y - obj.h);
  420. arrow.drawBottomToTop(context);
  421. }
  422. }
  423. //箭头从step矩形right——>left
  424. Step.prototype.drawRightToLeft = function(obj,context) {
  425. var arrow = null;
  426. if(obj.flag == "step") {
  427. arrow = new Arrow(this.x + this.w / 2, this.y, obj.x - obj.w / 2 , obj.y);
  428. } else if(obj.flag == "condition") {
  429. arrow = new Arrow(this.x + this.w / 2, this.y, obj.x-obj.l*2 , obj.y);
  430. }else if(obj.flag == "start" || obj.flag == "end"){
  431. arrow = new Arrow(this.x + this.w / 2, this.y, obj.x-obj.w/2 , obj.y);
  432. }
  433. arrow.drawLeftToRightOrRightToLeft(context);
  434. }
  435. //箭头从Condition菱形Bottom——>top
  436. Condition.prototype.drawBottomToTop = function(obj,context) {
  437. if(obj.flag == "step") {
  438. var arrow = new Arrow(this.x, this.y + this.h, obj.x, obj.y - obj.h / 2);
  439. arrow.drawBottomToTop(context);
  440. } else if(obj.flag == "condition") {
  441. var arrow = new Arrow(this.x, this.y + this.l, obj.x, obj.y - obj.l);
  442. arrow.drawBottomToTop(context);
  443. }
  444. }
  445. //箭头从Condition菱形right——>top
  446. Condition.prototype.drawRightToTop = function(obj, context) {
  447. if(obj.flag == "step") {
  448. var arrow = new Arrow(this.x + this.l * 2, this.y, obj.x, obj.y - obj.h / 2);
  449. arrow.drawLeftOrRightToTop(context);
  450. } else if(obj.flag == "condition") {
  451. var arrow = new Arrow(this.x + this.l * 2, this.y, obj.x, obj.y - obj.l);
  452. arrow.drawLeftOrRightToTop(context);
  453. }
  454. }
  455. //箭头从Condition菱形left——>top
  456. Condition.prototype.drawLeftToTop = function(obj,context) {
  457. if(obj.flag == "step") {
  458. var arrow = new Arrow(this.x - this.l * 2, this.y, obj.x, obj.y - obj.h / 2);
  459. arrow.drawLeftOrRightToTop(context);
  460. } else if(obj.flag == "condition") {
  461. var arrow = new Arrow(this.x - this.l * 2, this.y, obj.x, obj.y - obj.l);
  462. arrow.drawLeftOrRightToTop(context);
  463. }
  464. }
  465. //箭头从Condition菱形right——>left
  466. Condition.prototype.drawRightToLeft = function(obj,context) {
  467. if(obj.flag == "step") {
  468. var arrow = new Arrow(this.x + this.l * 2, this.y, obj.x - this.w / 2, obj.y);
  469. arrow.drawLeftToRightOrRightToLeft(context);
  470. } else if(obj.flag == "condition") {
  471. var arrow = new Arrow(this.x + this.l * 2, this.y, obj.x - this.l * 2, obj.y);
  472. arrow.drawLeftToRightOrRightToLeft(context);
  473. }
  474. }
  475. //箭头从Condition菱形left——>right
  476. Condition.prototype.drawLeftToRight = function(obj, context) {
  477. if(obj.flag == "step") {
  478. var arrow = new Arrow(this.x - this.l * 2, this.y, obj.x + this.w / 2, obj.y);
  479. arrow.drawLeftToRightOrRightToLeft(context);
  480. } else if(obj.flag == "condition") {
  481. var arrow = new Arrow(this.x - this.l * 2, this.y, obj.x + this.l * 2, obj.y);
  482. arrow.drawLeftToRightOrRightToLeft(context);
  483. }
  484. }
  485. /////////////////////////////////////////画箭头start/////////////////////////////////////
  486. function Arrow(x1, y1, x2, y2) {
  487. this.x1 = x1;
  488. this.y1 = y1;
  489. this.x2 = x2;
  490. this.y2 = y2;
  491. this.tmpX1 = null;
  492. this.tmpY1 = null;
  493. this.tmpX2 = null;
  494. this.tmpY2 = null;
  495. this.color = "#5B9BD5";
  496. }
  497. Arrow.prototype.setColor = function(color) {
  498. this.color=color;
  499. }
  500. /**
  501. *
  502. * @param {Object} x1起始点横坐标
  503. * @param {Object} y1起始点纵坐标
  504. * @param {Object} x2结束点横坐标
  505. * @param {Object} y2结束点纵坐标
  506. */
  507. Arrow.prototype.setP = function(x1, y1, x2, y2) {
  508. this.x1 = x1;
  509. this.y1 = y1;
  510. this.x2 = x2;
  511. this.y2 = y2;
  512. }
  513. //第一个拐点
  514. Arrow.prototype.setP1 = function(tmpX1,tmpY1) {
  515. this.tmpX1=tmpX1;
  516. this.tmpY1=tmpY1;
  517. }
  518. //第二个拐点
  519. Arrow.prototype.setP2 = function(tmpX2,tmpY2) {
  520. this.tmpX2=tmpX2;
  521. this.tmpY2=tmpY2;
  522. }
  523. Arrow.prototype.drawBottomToTop = function(ctx) {
  524. if (this.x1 != this.x2) {
  525. this.setP1(this.x1,(this.y1+this.y2)/2);
  526. this.setP2(this.x2,(this.y1+this.y2)/2);
  527. this.draw(ctx);
  528. }else{
  529. this.draw(ctx);
  530. }
  531. }
  532. Arrow.prototype.drawLeftOrRightToTop = function(ctx) {
  533. this.setP1(this.x2,this.y1);
  534. this.draw(ctx);
  535. }
  536. Arrow.prototype.drawLeftToRightOrRightToLeft = function(ctx) {
  537. if (this.y1 != this.y2) {
  538. this.setP1((this.x1+this.x2)/2,this.y1);
  539. this.setP2((this.x1+this.x2)/2,this.y2);
  540. this.draw(ctx);
  541. }else{
  542. this.draw(ctx);
  543. }
  544. }
  545. Arrow.prototype.drawBottomToRight = function(ctx) {
  546. if (this.y1 != this.y2) {
  547. this.setP1(this.x1,this.y2);
  548. this.draw(ctx);
  549. }else{
  550. this.draw(ctx);
  551. }
  552. }
  553. Arrow.prototype.draw = function(ctx) {
  554. // arbitrary styling
  555. ctx.strokeStyle = this.color;
  556. ctx.fillStyle = this.color;
  557. // draw the line
  558. ctx.beginPath();
  559. ctx.moveTo(this.x1, this.y1);
  560. if(this.tmpX1 != null && this.tmpY1 != null && this.tmpX2 != null && this.tmpY2 != null) {
  561. ctx.lineTo(this.tmpX1, this.tmpY1);
  562. ctx.closePath();
  563. ctx.stroke();
  564. ctx.beginPath();
  565. ctx.moveTo(this.tmpX1, this.tmpY1)
  566. ctx.lineTo(this.tmpX2, this.tmpY2);
  567. ctx.closePath();
  568. ctx.stroke();
  569. ctx.beginPath();
  570. ctx.moveTo(this.tmpX2, this.tmpY2);
  571. ctx.lineTo(this.x2, this.y2);
  572. ctx.closePath();
  573. ctx.stroke();
  574. var endRadians = Math.atan((this.y2 - this.tmpY2) / (this.x2 - this.tmpX2));
  575. endRadians += ((this.x2 >= this.tmpX2) ? 90 : -90) * Math.PI / 180;
  576. this.drawArrowhead(ctx, this.x2, this.y2, endRadians);
  577. } else if(this.tmpX1 != null && this.tmpY1 != null && this.tmpX2 == null && this.tmpY2 == null) {
  578. ctx.lineTo(this.tmpX1, this.tmpY1);
  579. ctx.closePath();
  580. ctx.stroke();
  581. ctx.beginPath();
  582. ctx.moveTo(this.tmpX1, this.tmpY1)
  583. ctx.lineTo(this.x2, this.y2);
  584. ctx.closePath();
  585. ctx.stroke();
  586. var endRadians = Math.atan((this.y2 - this.tmpY1) / (this.x2 - this.tmpX1));
  587. endRadians += ((this.x2 >= this.tmpX1) ? 90 : -90) * Math.PI / 180;
  588. this.drawArrowhead(ctx, this.x2, this.y2, endRadians);
  589. }else if(this.tmpX1 == null && this.tmpY1 == null && this.tmpX2 == null && this.tmpY2 == null){
  590. ctx.lineTo(this.x2, this.y2);
  591. ctx.closePath();
  592. ctx.stroke();
  593. var endRadians = Math.atan((this.y2 - this.y1) / (this.x2 - this.x1));
  594. endRadians += ((this.x2 >= this.x1) ? 90 : -90) * Math.PI / 180;
  595. this.drawArrowhead(ctx, this.x2, this.y2, endRadians);
  596. }
  597. }
  598. Arrow.prototype.drawArrowhead = function(ctx, x, y, radians) {
  599. ctx.save();
  600. ctx.beginPath();
  601. ctx.translate(x, y);
  602. ctx.rotate(radians);
  603. ctx.moveTo(0, 0);
  604. ctx.lineTo(5, 10);
  605. ctx.lineTo(-5, 10);
  606. ctx.closePath();
  607. ctx.restore();
  608. ctx.fill();
  609. }
  610. /////////////////////////////////////////画箭头end/////////////////////////////////////

html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <link rel="stylesheet" href="public.css">
  7. </head>
  8. <body>
  9. <div class="box">
  10. <canvas id="myCanvas" width="1000" height="800"></canvas>
  11. </div>
  12. <script type="text/javascript" src="drawFlowChart.js" ></script>
  13. <script>
  14. var canvas = document.getElementById("myCanvas");
  15. var cxt = canvas.getContext('2d');
  16. var canWidth = cxt.canvas.clientWidth;
  17. var init = {top: 32, spaceH: 70};
  18. var row2 = {
  19. y:init.top+init.spaceH,
  20. data:[
  21. {
  22. type:'Step',
  23. text:'业务名称1',
  24. name:'step_2_1',
  25. arrowArr:[
  26. {
  27. arrow:'drawBottomToTop',
  28. to:'step_3_1'
  29. }
  30. ],
  31. x:'',
  32. y:'',
  33. requestData:{}
  34. },
  35. {
  36. type:'Step',
  37. text:'业务名称2',
  38. name:'step_2_2',
  39. arrowArr:[
  40. {
  41. arrow:'drawBottomToLeft',
  42. to:'step_3_2'
  43. }
  44. ]
  45. },
  46. {
  47. type:'Step',
  48. text:'业务名称3',
  49. name:'step_2_3',
  50. arrowArr:[
  51. {
  52. arrow:'drawBottomToRight',
  53. to:'step_3_2'
  54. }
  55. ]
  56. },
  57. {
  58. type:'Step',
  59. name:'step_2_4',
  60. text:'业务名称4',
  61. arrowArr:[
  62. {
  63. arrow:'drawBottomToRight',
  64. to:'step_7_1'
  65. }
  66. ]
  67. }
  68. ]
  69. };
  70. row2.data = calChartX(canWidth,row2.data, row2.y);
  71. var flowData = [
  72. {
  73. row:1,
  74. y:init.top,
  75. data:[
  76. {
  77. type:'Start',
  78. text:'开始',
  79. name:'step_1_1',
  80. arrowArr:[
  81. {
  82. arrow:'drawBottomToTop',
  83. to:'step_2_1'
  84. },
  85. {
  86. arrow:'drawBottomToTop',
  87. to:'step_2_2'
  88. },
  89. {
  90. arrow:'drawBottomToTop',
  91. to:'step_2_3'
  92. }, {
  93. arrow:'drawBottomToTop',
  94. to:'step_2_4'
  95. }
  96. ],
  97. x:canWidth/2,
  98. y:''
  99. }
  100. ]
  101. },
  102. {
  103. row:2,
  104. y:init.top+init.spaceH,
  105. data:row2.data,
  106. method:{
  107. onmousemove:null,
  108. onmouseleave:null,
  109. onclick:hoverSingleChart
  110. }
  111. },
  112. {
  113. row:3,
  114. y:'',
  115. data:[{
  116. type:'Step',
  117. text:'业务名称4',
  118. x:row2.data[0].x,
  119. name:'step_3_1',
  120. arrowArr:[
  121. {
  122. arrow:'drawBottomToTop',
  123. to:'step_4_1'
  124. }
  125. ]
  126. },{
  127. type:'Step',
  128. text:'业务名称5',
  129. name:'step_3_2',
  130. x:canWidth/2,
  131. arrowArr:[
  132. {
  133. arrow:'drawBottomToTop',
  134. to:'step_4_2'
  135. }
  136. ]
  137. }]
  138. },
  139. {
  140. row:4,
  141. y:'',
  142. data:[{
  143. type:'Step',
  144. text:'业务名称6',
  145. x:row2.data[0].x,
  146. name:'step_4_1',
  147. arrowArr:[
  148. {
  149. arrow:'drawBottomToTop',
  150. to:'step_5_1'
  151. }
  152. ]
  153. },{
  154. type:'Step',
  155. text:'业务名称7',
  156. name:'step_4_2',
  157. x:canWidth/2,
  158. arrowArr:[
  159. {
  160. arrow:'drawBottomToTop',
  161. to:'step_5_2'
  162. }
  163. ]
  164. }]
  165. },
  166. {
  167. row:5,
  168. y:'',
  169. data:[{
  170. type:'Step',
  171. text:'业务名称8',
  172. x:row2.data[0].x,
  173. name:'step_5_1',
  174. arrowArr:[
  175. {
  176. arrow:'drawBottomToTop',
  177. to:'step_6_1'
  178. }
  179. ]
  180. },{
  181. type:'Step',
  182. text:'业务名称9',
  183. name:'step_5_2',
  184. x:canWidth/2,
  185. arrowArr:[
  186. {
  187. arrow:'drawBottomToTop',
  188. to:'step_6_2'
  189. },
  190. {
  191. arrow:'drawBottomToTop',
  192. to:'step_6_3'
  193. }
  194. ]
  195. }]
  196. },
  197. {
  198. row:6,
  199. y:'',
  200. data:[{
  201. type:'Step',
  202. text:'业务名称10',
  203. x:row2.data[0].x,
  204. name:'step_6_1',
  205. arrowArr:[
  206. {
  207. arrow:'drawBottomToLeft',
  208. to:'step_7_1'
  209. }
  210. ]
  211. },{
  212. type:'Step',
  213. text:'业务名称11',
  214. name:'step_6_2',
  215. x:row2.data[1].x,
  216. arrowArr:[
  217. {
  218. arrow:'drawBottomToTop',
  219. to:'step_7_1'
  220. }
  221. ]
  222. },{
  223. type:'Step',
  224. text:'业务名称12',
  225. name:'step_6_3',
  226. x:row2.data[2].x,
  227. arrowArr:[
  228. {
  229. arrow:'drawBottomToTop',
  230. to:'step_7_1'
  231. }
  232. ]
  233. }]
  234. },
  235. {
  236. row:7,
  237. y:init.top+init.spaceH*6+10,
  238. data:[{
  239. type:'Condition',
  240. text:'判断条件',
  241. x:canWidth/2,
  242. name:'step_7_1',
  243. arrowArr:[
  244. {
  245. arrow:'drawBottomToTop',
  246. to:'step_8_1'
  247. }
  248. ]
  249. }]
  250. },
  251. {
  252. row:8,
  253. y:init.top+init.spaceH*7+30,
  254. isAverage:true, //平均计算x
  255. data:[
  256. {
  257. type:'Step',
  258. text:'业务名称12',
  259. name:'step_8_1',
  260. arrowArr:[
  261. {
  262. arrow:'drawRightToLeft',
  263. to:'step_8_2'
  264. }
  265. ],
  266. requestData:{},
  267. method:{
  268. onmousemove:null,
  269. onmouseleave:null,
  270. onclick:null
  271. }
  272. },
  273. {
  274. type:'Step',
  275. text:'业务名称4',
  276. name:'step_8_2',
  277. arrowArr:[
  278. {
  279. arrow:'drawRightToLeft',
  280. to:'step_8_3'
  281. }
  282. ]
  283. },
  284. {
  285. type:'Step',
  286. text:'业务名称4',
  287. name:'step_8_3',
  288. arrowArr:[
  289. {
  290. arrow:'drawRightToLeft',
  291. to:'step_8_4'
  292. }
  293. ]
  294. },
  295. {
  296. type:'Step',
  297. name:'step_8_4',
  298. text:'业务名称4',
  299. arrowArr:[
  300. {
  301. arrow:'drawRightToLeft',
  302. to:'step_8_5'
  303. }
  304. ]
  305. },
  306. {
  307. type:'Step',
  308. name:'step_8_5',
  309. text:'业务名称4',
  310. arrowArr:[
  311. {
  312. arrow:'drawRightToLeft',
  313. to:'step_8_6'
  314. }
  315. ]
  316. },
  317. {
  318. type:'Step',
  319. name:'step_8_6',
  320. text:'业务名称4',
  321. arrowArr:[
  322. {
  323. arrow:'drawBottomToTop',
  324. to:'step_9_1'
  325. }
  326. ]
  327. }
  328. ]
  329. },
  330. {
  331. row:9,
  332. y:init.top+init.spaceH*8+30,
  333. isAverage:true,
  334. data:[
  335. {
  336. type:'Step',
  337. text:'业务名称4',
  338. name:'step_9_1',
  339. arrowArr:[
  340. {
  341. arrow:'drawRightToLeft',
  342. to:'step_9_2'
  343. }
  344. ],
  345. requestData:{},
  346. method:{
  347. onmousemove:null,
  348. onmouseleave:null,
  349. onclick:hoverSingleChart
  350. }
  351. },
  352. {
  353. type:'Step',
  354. text:'业务名称4',
  355. name:'step_9_2',
  356. arrowArr:[
  357. {
  358. arrow:'drawRightToLeft',
  359. to:'step_9_3'
  360. }
  361. ]
  362. },
  363. {
  364. type:'Step',
  365. text:'业务名称4',
  366. name:'step_9_3',
  367. arrowArr:[
  368. {
  369. arrow:'drawRightToLeft',
  370. to:'step_9_4'
  371. }
  372. ]
  373. },
  374. {
  375. type:'Step',
  376. name:'step_9_4',
  377. text:'业务名称4',
  378. arrowArr:[
  379. {
  380. arrow:'drawRightToLeft',
  381. to:'step_9_5'
  382. }
  383. ]
  384. },
  385. {
  386. type:'End',
  387. name:'step_9_5',
  388. text:'结束',
  389. arrowArr:[]
  390. }
  391. ]
  392. }
  393. ];
  394. drawFlowChart(cxt,canvas,flowData, init.top, init.spaceH);
  395. function hoverSingleChart(singleData){
  396. console.log("---------鼠标事件-----------");
  397. console.log(singleData);
  398. }
  399. </script>
  400. </body>
  401. </html>

参考博文:https://www.cnblogs.com/DurantSimpson/p/6146038.html/

canvas绘制流程图的更多相关文章

  1. 记录使用echarts的graph类型绘制流程图全过程(二)- 多层关系和圆形图片的设置

    本文主要记录在使用echarts的graph类型绘制流程图时候遇到的2个问题:对于圆形图片的剪切和多层关系的设置 图片的设置 如果用echarts默认的symbol参数来显示图片,会显示图片的原始状态 ...

  2. 玩转控件:GDI+动态绘制流程图

       前言 今天,要跟大家一起分享是"GDI+动态生成流程图"的功能.别看名字高大上(也就那样儿--!),其实就是动态生成控件,然后GDI+绘制直线连接控件罢了.实际项目效果图如下 ...

  3. HTML5学习总结——canvas绘制象棋(canvas绘图)

    一.HTML5学习总结——canvas绘制象棋 1.第一次:canvas绘制象棋(笨方法)示例代码: <!DOCTYPE html> <html> <head> & ...

  4. 用canvas绘制折线图

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. 封装 用canvas绘制直线的函数--面向对象

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. 学习笔记:HTML5 Canvas绘制简单图形

    HTML5 Canvas绘制简单图形 1.添加Canvas标签,添加id供js操作. <canvas id="mycanvas" height="700" ...

  7. canvas绘制经典折线图(一)

    最终效果图如下: 实现步骤如下:注-引用了jQuery HTML代码 <!doctype html> <html lang="en"> <head&g ...

  8. Canvas绘制图形

    1.Canvas绘制一个蓝色的矩形 <!DOCTYPE html> <html> <head lang="en"> <meta chars ...

  9. [canvas]利用canvas绘制自适应的折线图

    前段时间学习了用canvas绘制折现图,且当画布变换大小,折现图会随之变化,现附上代码 <!DOCTYPE html> <html lang="en"> & ...

随机推荐

  1. wx-show与!show

    切换的表示 <!--index.wxml--> <view class="container"> <view class="item&quo ...

  2. 一篇文章,彻底理解ReentrantLock

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  3. CSS 从大图中选取部分区域作为目标图标

    从大图中选取部分区域作为目标图标 by:授客 QQ:1033553122 1.图片素材 图片素材如下(大小:137px * 264px),图中从上到下,连续存放了两张100px * 100px的图   ...

  4. Nginx一般配置

    文件为 nginx.conf ,一般配置内容如下: user root;worker_processes auto;#pid /var/run/nginx.pid;#error_log /dev/st ...

  5. WORD 和Uint16的区别

    UINT   A 16-bit unsigned integer on Windows versions 3.0 and 3.1; a 32-bit unsigned integer on Win32 ...

  6. 如何使用 TRANSPORTABLE = ALWAYS 将PDB移回Non-CDB (Doc ID 2027352.1)

    How to Move a PDB Back to a Non-CDB Using TRANSPORTABLE=ALWAYS (Doc ID 2027352.1) APPLIES TO: Oracle ...

  7. 打包Python文件为exe

    pip install pyinstaller 然后就在终端里执行命令 cd 到目标文件的目录下 执行 pyinstaller  -F  ***.py 即可生成exe

  8. [PHP] 解决php中上传大文件的错误

    修改nginx配置文件,下面这个参数client_max_body_size 110M; 修改php配置文件中下面两个参数在php.ini中找到下面两个配置,配置项给改大,如果找不到php.ini的位 ...

  9. Ubuntu下doxygen+graphviz使用概录

    关键词:doxygen.Doxyfile.doxywizard.dot.graphviz等等. 使用doxygen从源码注释生成帮助文档或者SDK,输出格式有多种比如htmp.Latex等等. 如果想 ...

  10. 【转】关闭firefox火狐浏览器下载完成时自动扫描(49.0.2以后版本)

    用firefox火狐浏览器下载文件到最后时,会显示"剩余时间未知",将持续10秒钟左右,即使几KB 的文件,也要持续这么长时间,问度娘才知道是自动扫描,检查是否有毒,用的却是Goo ...