You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

398 lines
10 KiB

  1. /*
  2. * Copyright (c) 2011, Terrence Lee <kill889@gmail.com>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the <organization> nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  17. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. var gitGraph = function (canvas, rawGraphList, config) {
  28. if (!canvas.getContext) {
  29. return;
  30. }
  31. if (typeof config === "undefined") {
  32. config = {
  33. unitSize: 20,
  34. lineWidth: 3,
  35. nodeRadius: 4
  36. };
  37. }
  38. var flows = [];
  39. var graphList = [];
  40. var ctx = canvas.getContext("2d");
  41. var init = function () {
  42. var maxWidth = 0;
  43. var i;
  44. var l = rawGraphList.length;
  45. var row;
  46. var midStr;
  47. for (i = 0; i < l; i++) {
  48. midStr = rawGraphList[i].replace(/\s+/g, " ").replace(/^\s+|\s+$/g, "");
  49. maxWidth = Math.max(midStr.replace(/(\_|\s)/g, "").length, maxWidth);
  50. row = midStr.split("");
  51. graphList.unshift(row);
  52. }
  53. canvas.width = maxWidth * config.unitSize;
  54. canvas.height = graphList.length * config.unitSize;
  55. ctx.lineWidth = config.lineWidth;
  56. ctx.lineJoin = "round";
  57. ctx.lineCap = "round";
  58. };
  59. var genRandomStr = function () {
  60. var chars = "0123456789ABCDEF";
  61. var stringLength = 6;
  62. var randomString = '', rnum, i;
  63. for (i = 0; i < stringLength; i++) {
  64. rnum = Math.floor(Math.random() * chars.length);
  65. randomString += chars.substring(rnum, rnum + 1);
  66. }
  67. return randomString;
  68. };
  69. var findFlow = function (id) {
  70. var i = flows.length;
  71. while (i-- && flows[i].id !== id) {}
  72. return i;
  73. };
  74. var findColomn = function (symbol, row) {
  75. var i = row.length;
  76. while (i-- && row[i] !== symbol) {}
  77. return i;
  78. };
  79. var findBranchOut = function (row) {
  80. if (!row) {
  81. return -1
  82. }
  83. var i = row.length;
  84. while (i-- &&
  85. !(row[i - 1] && row[i] === "/" && row[i - 1] === "|") &&
  86. !(row[i - 2] && row[i] === "_" && row[i - 2] === "|")) {}
  87. return i;
  88. }
  89. var genNewFlow = function () {
  90. var newId;
  91. do {
  92. newId = genRandomStr();
  93. } while (findFlow(newId) !== -1);
  94. return {id:newId, color:"#" + newId};
  95. };
  96. //draw method
  97. var drawLineRight = function (x, y, color) {
  98. ctx.strokeStyle = color;
  99. ctx.beginPath();
  100. ctx.moveTo(x, y + config.unitSize / 2);
  101. ctx.lineTo(x + config.unitSize, y + config.unitSize / 2);
  102. ctx.stroke();
  103. };
  104. var drawLineUp = function (x, y, color) {
  105. ctx.strokeStyle = color;
  106. ctx.beginPath();
  107. ctx.moveTo(x, y + config.unitSize / 2);
  108. ctx.lineTo(x, y - config.unitSize / 2);
  109. ctx.stroke();
  110. };
  111. var drawNode = function (x, y, color) {
  112. ctx.strokeStyle = color;
  113. drawLineUp(x, y, color);
  114. ctx.beginPath();
  115. ctx.arc(x, y, config.nodeRadius, 0, Math.PI * 2, true);
  116. ctx.fill();
  117. };
  118. var drawLineIn = function (x, y, color) {
  119. ctx.strokeStyle = color;
  120. ctx.beginPath();
  121. ctx.moveTo(x + config.unitSize, y + config.unitSize / 2);
  122. ctx.lineTo(x, y - config.unitSize / 2);
  123. ctx.stroke();
  124. };
  125. var drawLineOut = function (x, y, color) {
  126. ctx.strokeStyle = color;
  127. ctx.beginPath();
  128. ctx.moveTo(x, y + config.unitSize / 2);
  129. ctx.lineTo(x + config.unitSize, y - config.unitSize / 2);
  130. ctx.stroke();
  131. };
  132. var draw = function (graphList) {
  133. var colomn, colomnIndex, prevColomn, condenseIndex;
  134. var x, y;
  135. var color;
  136. var nodePos, outPos;
  137. var tempFlow;
  138. var prevRowLength = 0;
  139. var flowSwapPos = -1;
  140. var lastLinePos;
  141. var i, k, l;
  142. var condenseCurrentLength, condensePrevLength = 0, condenseNextLength = 0;
  143. var inlineIntersect = false;
  144. //initiate for first row
  145. for (i = 0, l = graphList[0].length; i < l; i++) {
  146. if (graphList[0][i] !== "_" && graphList[0][i] !== " ") {
  147. flows.push(genNewFlow());
  148. }
  149. }
  150. y = canvas.height - config.unitSize * 0.5;
  151. //iterate
  152. for (i = 0, l = graphList.length; i < l; i++) {
  153. x = config.unitSize * 0.5;
  154. currentRow = graphList[i];
  155. nextRow = graphList[i + 1];
  156. prevRow = graphList[i - 1];
  157. flowSwapPos = -1;
  158. condenseCurrentLength = currentRow.filter(function (val) {
  159. return (val !== " " && val !== "_")
  160. }).length;
  161. if (nextRow) {
  162. condenseNextLength = nextRow.filter(function (val) {
  163. return (val !== " " && val !== "_")
  164. }).length;
  165. } else {
  166. condenseNextLength = 0;
  167. }
  168. //pre process begin
  169. //use last row for analysing
  170. if (prevRow) {
  171. if (!inlineIntersect) {
  172. //intersect might happen
  173. for (colomnIndex = 0; colomnIndex < prevRowLength; colomnIndex++) {
  174. if (prevRow[colomnIndex + 1] &&
  175. (prevRow[colomnIndex] === "/" && prevRow[colomnIndex + 1] === "|") ||
  176. ((prevRow[colomnIndex] === "_" && prevRow[colomnIndex + 1] === "|") &&
  177. (prevRow[colomnIndex + 2] === "/"))) {
  178. flowSwapPos = colomnIndex;
  179. //swap two flow
  180. tempFlow = {id:flows[flowSwapPos].id, color:flows[flowSwapPos].color};
  181. flows[flowSwapPos].id = flows[flowSwapPos + 1].id;
  182. flows[flowSwapPos].color = flows[flowSwapPos + 1].color;
  183. flows[flowSwapPos + 1].id = tempFlow.id;
  184. flows[flowSwapPos + 1].color = tempFlow.color;
  185. }
  186. }
  187. }
  188. if (condensePrevLength < condenseCurrentLength &&
  189. ((nodePos = findColomn("*", currentRow)) !== -1 &&
  190. (findColomn("_", currentRow) === -1))) {
  191. flows.splice(nodePos - 1, 0, genNewFlow());
  192. }
  193. if (prevRowLength > currentRow.length &&
  194. (nodePos = findColomn("*", prevRow)) !== -1) {
  195. if (findColomn("_", currentRow) === -1 &&
  196. findColomn("/", currentRow) === -1 &&
  197. findColomn("\\", currentRow) === -1) {
  198. flows.splice(nodePos + 1, 1);
  199. }
  200. }
  201. } //done with the previous row
  202. prevRowLength = currentRow.length; //store for next round
  203. colomnIndex = 0; //reset index
  204. condenseIndex = 0;
  205. condensePrevLength = 0;
  206. while (colomnIndex < currentRow.length) {
  207. colomn = currentRow[colomnIndex];
  208. if (colomn !== " " && colomn !== "_") {
  209. ++condensePrevLength;
  210. }
  211. if (colomn === " " &&
  212. currentRow[colomnIndex + 1] &&
  213. currentRow[colomnIndex + 1] === "_" &&
  214. currentRow[colomnIndex - 1] &&
  215. currentRow[colomnIndex - 1] === "|") {
  216. currentRow.splice(colomnIndex, 1);
  217. currentRow[colomnIndex] = "/";
  218. colomn = "/";
  219. }
  220. //create new flow only when no intersetc happened
  221. if (flowSwapPos === -1 &&
  222. colomn === "/" &&
  223. currentRow[colomnIndex - 1] &&
  224. currentRow[colomnIndex - 1] === "|") {
  225. flows.splice(condenseIndex, 0, genNewFlow());
  226. }
  227. //change \ and / to | when it's in the last position of the whole row
  228. if (colomn === "/" || colomn === "\\") {
  229. if (!(colomn === "/" && findBranchOut(nextRow) === -1)) {
  230. if ((lastLinePos = Math.max(findColomn("|", currentRow),
  231. findColomn("*", currentRow))) !== -1 &&
  232. (lastLinePos < colomnIndex - 1)) {
  233. while (currentRow[++lastLinePos] === " ") {}
  234. if (lastLinePos === colomnIndex) {
  235. currentRow[colomnIndex] = "|";
  236. }
  237. }
  238. }
  239. }
  240. if (colomn === "*" &&
  241. prevRow &&
  242. prevRow[condenseIndex + 1] === "\\") {
  243. flows.splice(condenseIndex + 1, 1);
  244. }
  245. if (colomn !== " ") {
  246. ++condenseIndex;
  247. }
  248. ++colomnIndex;
  249. }
  250. condenseCurrentLength = currentRow.filter(function (val) {
  251. return (val !== " " && val !== "_")
  252. }).length;
  253. //do some clean up
  254. if (flows.length > condenseCurrentLength) {
  255. flows.splice(condenseCurrentLength, flows.length - condenseCurrentLength);
  256. }
  257. colomnIndex = 0;
  258. //a little inline analysis and draw process
  259. while (colomnIndex < currentRow.length) {
  260. colomn = currentRow[colomnIndex];
  261. prevColomn = currentRow[colomnIndex - 1];
  262. if (currentRow[colomnIndex] === " ") {
  263. currentRow.splice(colomnIndex, 1);
  264. x += config.unitSize;
  265. continue;
  266. }
  267. //inline interset
  268. if ((colomn === "_" || colomn === "/") &&
  269. currentRow[colomnIndex - 1] === "|" &&
  270. currentRow[colomnIndex - 2] === "_") {
  271. inlineIntersect = true;
  272. tempFlow = flows.splice(colomnIndex - 2, 1)[0];
  273. flows.splice(colomnIndex - 1, 0, tempFlow);
  274. currentRow.splice(colomnIndex - 2, 1);
  275. colomnIndex = colomnIndex - 1;
  276. } else {
  277. inlineIntersect = false;
  278. }
  279. color = flows[colomnIndex].color;
  280. switch (colomn) {
  281. case "_" :
  282. drawLineRight(x, y, color);
  283. x += config.unitSize;
  284. break;
  285. case "*" :
  286. drawNode(x, y, color);
  287. break;
  288. case "|" :
  289. drawLineUp(x, y, color);
  290. break;
  291. case "/" :
  292. if (prevColomn &&
  293. (prevColomn === "/" ||
  294. prevColomn === " ")) {
  295. x -= config.unitSize;
  296. }
  297. drawLineOut(x, y, color);
  298. x += config.unitSize;
  299. break;
  300. case "\\" :
  301. drawLineIn(x, y, color);
  302. break;
  303. }
  304. ++colomnIndex;
  305. }
  306. y -= config.unitSize;
  307. }
  308. };
  309. init();
  310. draw(graphList);
  311. };