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.

gitgraph.js 11KB

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