|
|
@ -0,0 +1,399 @@ |
|
|
|
/* |
|
|
|
* Copyright (c) 2011, Terrence Lee <kill889@gmail.com> |
|
|
|
* All rights reserved. |
|
|
|
* |
|
|
|
* Redistribution and use in source and binary forms, with or without |
|
|
|
* modification, are permitted provided that the following conditions are met: |
|
|
|
* * Redistributions of source code must retain the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer. |
|
|
|
* * Redistributions in binary form must reproduce the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer in the |
|
|
|
* documentation and/or other materials provided with the distribution. |
|
|
|
* * Neither the name of the <organization> nor the |
|
|
|
* names of its contributors may be used to endorse or promote products |
|
|
|
* derived from this software without specific prior written permission. |
|
|
|
* |
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
|
|
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY |
|
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
|
*/ |
|
|
|
|
|
|
|
var gitGraph = function (canvas, rawGraphList, config) { |
|
|
|
if (!canvas.getContext) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (typeof config === "undefined") { |
|
|
|
config = { |
|
|
|
unitSize: 20, |
|
|
|
lineWidth: 3, |
|
|
|
nodeRadius: 4 |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
var flows = []; |
|
|
|
var graphList = []; |
|
|
|
|
|
|
|
var ctx = canvas.getContext("2d"); |
|
|
|
|
|
|
|
var init = function () { |
|
|
|
var maxWidth = 0; |
|
|
|
var i; |
|
|
|
var l = rawGraphList.length; |
|
|
|
var row; |
|
|
|
var midStr; |
|
|
|
|
|
|
|
for (i = 0; i < l; i++) { |
|
|
|
midStr = rawGraphList[i].replace(/\s+/g, " ").replace(/^\s+|\s+$/g, ""); |
|
|
|
|
|
|
|
maxWidth = Math.max(midStr.replace(/(\_|\s)/g, "").length, maxWidth); |
|
|
|
|
|
|
|
row = midStr.split(""); |
|
|
|
|
|
|
|
graphList.unshift(row); |
|
|
|
} |
|
|
|
|
|
|
|
canvas.width = maxWidth * config.unitSize; |
|
|
|
canvas.height = graphList.length * config.unitSize; |
|
|
|
|
|
|
|
ctx.lineWidth = config.lineWidth; |
|
|
|
ctx.lineJoin = "round"; |
|
|
|
ctx.lineCap = "round"; |
|
|
|
}; |
|
|
|
|
|
|
|
var genRandomStr = function () { |
|
|
|
var chars = "0123456789ABCDEF"; |
|
|
|
var stringLength = 6; |
|
|
|
var randomString = '', rnum, i; |
|
|
|
for (i = 0; i < stringLength; i++) { |
|
|
|
rnum = Math.floor(Math.random() * chars.length); |
|
|
|
randomString += chars.substring(rnum, rnum + 1); |
|
|
|
} |
|
|
|
|
|
|
|
return randomString; |
|
|
|
}; |
|
|
|
|
|
|
|
var findFlow = function (id) { |
|
|
|
var i = flows.length; |
|
|
|
|
|
|
|
while (i-- && flows[i].id !== id) {} |
|
|
|
|
|
|
|
return i; |
|
|
|
}; |
|
|
|
|
|
|
|
var findColomn = function (symbol, row) { |
|
|
|
var i = row.length; |
|
|
|
|
|
|
|
while (i-- && row[i] !== symbol) {} |
|
|
|
|
|
|
|
return i; |
|
|
|
}; |
|
|
|
|
|
|
|
var findBranchOut = function (row) { |
|
|
|
if (!row) { |
|
|
|
return -1 |
|
|
|
} |
|
|
|
|
|
|
|
var i = row.length; |
|
|
|
|
|
|
|
while (i-- && |
|
|
|
!(row[i - 1] && row[i] === "/" && row[i - 1] === "|") && |
|
|
|
!(row[i - 2] && row[i] === "_" && row[i - 2] === "|")) {} |
|
|
|
|
|
|
|
return i; |
|
|
|
} |
|
|
|
|
|
|
|
var genNewFlow = function () { |
|
|
|
var newId; |
|
|
|
|
|
|
|
do { |
|
|
|
newId = genRandomStr(); |
|
|
|
} while (findFlow(newId) !== -1); |
|
|
|
|
|
|
|
return {id:newId, color:"#" + newId}; |
|
|
|
}; |
|
|
|
|
|
|
|
//draw method
|
|
|
|
var drawLineRight = function (x, y, color) { |
|
|
|
ctx.strokeStyle = color; |
|
|
|
ctx.beginPath(); |
|
|
|
ctx.moveTo(x, y + config.unitSize / 2); |
|
|
|
ctx.lineTo(x + config.unitSize, y + config.unitSize / 2); |
|
|
|
ctx.stroke(); |
|
|
|
}; |
|
|
|
|
|
|
|
var drawLineUp = function (x, y, color) { |
|
|
|
ctx.strokeStyle = color; |
|
|
|
ctx.beginPath(); |
|
|
|
ctx.moveTo(x, y + config.unitSize / 2); |
|
|
|
ctx.lineTo(x, y - config.unitSize / 2); |
|
|
|
ctx.stroke(); |
|
|
|
}; |
|
|
|
|
|
|
|
var drawNode = function (x, y, color) { |
|
|
|
ctx.strokeStyle = color; |
|
|
|
|
|
|
|
drawLineUp(x, y, color); |
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
|
ctx.arc(x, y, config.nodeRadius, 0, Math.PI * 2, true); |
|
|
|
ctx.fill(); |
|
|
|
}; |
|
|
|
|
|
|
|
var drawLineIn = function (x, y, color) { |
|
|
|
ctx.strokeStyle = color; |
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
|
ctx.moveTo(x + config.unitSize, y + config.unitSize / 2); |
|
|
|
ctx.lineTo(x, y - config.unitSize / 2); |
|
|
|
ctx.stroke(); |
|
|
|
}; |
|
|
|
|
|
|
|
var drawLineOut = function (x, y, color) { |
|
|
|
ctx.strokeStyle = color; |
|
|
|
ctx.beginPath(); |
|
|
|
ctx.moveTo(x, y + config.unitSize / 2); |
|
|
|
ctx.lineTo(x + config.unitSize, y - config.unitSize / 2); |
|
|
|
ctx.stroke(); |
|
|
|
}; |
|
|
|
|
|
|
|
var draw = function (graphList) { |
|
|
|
var colomn, colomnIndex, prevColomn, condenseIndex; |
|
|
|
var x, y; |
|
|
|
var color; |
|
|
|
var nodePos, outPos; |
|
|
|
var tempFlow; |
|
|
|
var prevRowLength = 0; |
|
|
|
var flowSwapPos = -1; |
|
|
|
var lastLinePos; |
|
|
|
var i, k, l; |
|
|
|
var condenseCurrentLength, condensePrevLength = 0, condenseNextLength = 0; |
|
|
|
|
|
|
|
var inlineIntersect = false; |
|
|
|
|
|
|
|
//initiate for first row
|
|
|
|
for (i = 0, l = graphList[0].length; i < l; i++) { |
|
|
|
if (graphList[0][i] !== "_" && graphList[0][i] !== " ") { |
|
|
|
flows.push(genNewFlow()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
y = canvas.height - config.unitSize * 0.5; |
|
|
|
|
|
|
|
//iterate
|
|
|
|
for (i = 0, l = graphList.length; i < l; i++) { |
|
|
|
x = config.unitSize * 0.5; |
|
|
|
|
|
|
|
currentRow = graphList[i]; |
|
|
|
nextRow = graphList[i + 1]; |
|
|
|
prevRow = graphList[i - 1]; |
|
|
|
|
|
|
|
flowSwapPos = -1; |
|
|
|
|
|
|
|
condenseCurrentLength = currentRow.filter(function (val) { |
|
|
|
return (val !== " " && val !== "_") |
|
|
|
}).length; |
|
|
|
|
|
|
|
if (nextRow) { |
|
|
|
condenseNextLength = nextRow.filter(function (val) { |
|
|
|
return (val !== " " && val !== "_") |
|
|
|
}).length; |
|
|
|
} else { |
|
|
|
condenseNextLength = 0; |
|
|
|
} |
|
|
|
|
|
|
|
//pre process begin
|
|
|
|
//use last row for analysing
|
|
|
|
if (prevRow) { |
|
|
|
if (!inlineIntersect) { |
|
|
|
//intersect might happen
|
|
|
|
for (colomnIndex = 0; colomnIndex < prevRowLength; colomnIndex++) { |
|
|
|
if (prevRow[colomnIndex + 1] && |
|
|
|
(prevRow[colomnIndex] === "/" && prevRow[colomnIndex + 1] === "|") || |
|
|
|
((prevRow[colomnIndex] === "_" && prevRow[colomnIndex + 1] === "|") && |
|
|
|
(prevRow[colomnIndex + 2] === "/"))) { |
|
|
|
|
|
|
|
flowSwapPos = colomnIndex; |
|
|
|
|
|
|
|
//swap two flow
|
|
|
|
tempFlow = {id:flows[flowSwapPos].id, color:flows[flowSwapPos].color}; |
|
|
|
|
|
|
|
flows[flowSwapPos].id = flows[flowSwapPos + 1].id; |
|
|
|
flows[flowSwapPos].color = flows[flowSwapPos + 1].color; |
|
|
|
|
|
|
|
flows[flowSwapPos + 1].id = tempFlow.id; |
|
|
|
flows[flowSwapPos + 1].color = tempFlow.color; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (condensePrevLength < condenseCurrentLength && |
|
|
|
((nodePos = findColomn("*", currentRow)) !== -1 && |
|
|
|
(findColomn("_", currentRow) === -1))) { |
|
|
|
|
|
|
|
flows.splice(nodePos - 1, 0, genNewFlow()); |
|
|
|
} |
|
|
|
|
|
|
|
if (prevRowLength > currentRow.length && |
|
|
|
(nodePos = findColomn("*", prevRow)) !== -1) { |
|
|
|
|
|
|
|
if (findColomn("_", currentRow) === -1 && |
|
|
|
findColomn("/", currentRow) === -1 && |
|
|
|
findColomn("\\", currentRow) === -1) { |
|
|
|
|
|
|
|
flows.splice(nodePos + 1, 1); |
|
|
|
} |
|
|
|
} |
|
|
|
} //done with the previous row
|
|
|
|
|
|
|
|
prevRowLength = currentRow.length; //store for next round
|
|
|
|
colomnIndex = 0; //reset index
|
|
|
|
condenseIndex = 0; |
|
|
|
condensePrevLength = 0; |
|
|
|
while (colomnIndex < currentRow.length) { |
|
|
|
colomn = currentRow[colomnIndex]; |
|
|
|
|
|
|
|
if (colomn !== " " && colomn !== "_") { |
|
|
|
++condensePrevLength; |
|
|
|
} |
|
|
|
|
|
|
|
if (colomn === " " && |
|
|
|
currentRow[colomnIndex + 1] && |
|
|
|
currentRow[colomnIndex + 1] === "_" && |
|
|
|
currentRow[colomnIndex - 1] && |
|
|
|
currentRow[colomnIndex - 1] === "|") { |
|
|
|
|
|
|
|
currentRow.splice(colomnIndex, 1); |
|
|
|
|
|
|
|
currentRow[colomnIndex] = "/"; |
|
|
|
colomn = "/"; |
|
|
|
} |
|
|
|
|
|
|
|
//create new flow only when no intersetc happened
|
|
|
|
if (flowSwapPos === -1 && |
|
|
|
colomn === "/" && |
|
|
|
currentRow[colomnIndex - 1] && |
|
|
|
currentRow[colomnIndex - 1] === "|") { |
|
|
|
|
|
|
|
flows.splice(condenseIndex, 0, genNewFlow()); |
|
|
|
} |
|
|
|
|
|
|
|
//change \ and / to | when it's in the last position of the whole row
|
|
|
|
if (colomn === "/" || colomn === "\\") { |
|
|
|
if (!(colomn === "/" && findBranchOut(nextRow) === -1)) { |
|
|
|
if ((lastLinePos = Math.max(findColomn("|", currentRow), |
|
|
|
findColomn("*", currentRow))) !== -1 && |
|
|
|
(lastLinePos < colomnIndex - 1)) { |
|
|
|
|
|
|
|
while (currentRow[++lastLinePos] === " ") {} |
|
|
|
|
|
|
|
if (lastLinePos === colomnIndex) { |
|
|
|
currentRow[colomnIndex] = "|"; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (colomn === "*" && |
|
|
|
prevRow && |
|
|
|
prevRow[condenseIndex + 1] === "\\") { |
|
|
|
flows.splice(condenseIndex + 1, 1); |
|
|
|
} |
|
|
|
|
|
|
|
if (colomn !== " ") { |
|
|
|
++condenseIndex; |
|
|
|
} |
|
|
|
|
|
|
|
++colomnIndex; |
|
|
|
} |
|
|
|
|
|
|
|
condenseCurrentLength = currentRow.filter(function (val) { |
|
|
|
return (val !== " " && val !== "_") |
|
|
|
}).length; |
|
|
|
|
|
|
|
//do some clean up
|
|
|
|
if (flows.length > condenseCurrentLength) { |
|
|
|
flows.splice(condenseCurrentLength, flows.length - condenseCurrentLength); |
|
|
|
} |
|
|
|
|
|
|
|
colomnIndex = 0; |
|
|
|
|
|
|
|
//a little inline analysis and draw process
|
|
|
|
while (colomnIndex < currentRow.length) { |
|
|
|
colomn = currentRow[colomnIndex]; |
|
|
|
prevColomn = currentRow[colomnIndex - 1]; |
|
|
|
|
|
|
|
if (currentRow[colomnIndex] === " ") { |
|
|
|
currentRow.splice(colomnIndex, 1); |
|
|
|
x += config.unitSize; |
|
|
|
|
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
//inline interset
|
|
|
|
if ((colomn === "_" || colomn === "/") && |
|
|
|
currentRow[colomnIndex - 1] === "|" && |
|
|
|
currentRow[colomnIndex - 2] === "_") { |
|
|
|
|
|
|
|
inlineIntersect = true; |
|
|
|
|
|
|
|
tempFlow = flows.splice(colomnIndex - 2, 1)[0]; |
|
|
|
flows.splice(colomnIndex - 1, 0, tempFlow); |
|
|
|
currentRow.splice(colomnIndex - 2, 1); |
|
|
|
|
|
|
|
colomnIndex = colomnIndex - 1; |
|
|
|
} else { |
|
|
|
inlineIntersect = false; |
|
|
|
} |
|
|
|
|
|
|
|
color = flows[colomnIndex].color; |
|
|
|
|
|
|
|
switch (colomn) { |
|
|
|
case "_" : |
|
|
|
drawLineRight(x, y, color); |
|
|
|
|
|
|
|
x += config.unitSize; |
|
|
|
break; |
|
|
|
|
|
|
|
case "*" : |
|
|
|
drawNode(x, y, color); |
|
|
|
break; |
|
|
|
|
|
|
|
case "|" : |
|
|
|
drawLineUp(x, y, color); |
|
|
|
break; |
|
|
|
|
|
|
|
case "/" : |
|
|
|
if (prevColomn && |
|
|
|
(prevColomn === "/" || |
|
|
|
prevColomn === " ")) { |
|
|
|
x -= config.unitSize; |
|
|
|
} |
|
|
|
|
|
|
|
drawLineOut(x, y, color); |
|
|
|
|
|
|
|
x += config.unitSize; |
|
|
|
break; |
|
|
|
|
|
|
|
case "\\" : |
|
|
|
drawLineIn(x, y, color); |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
++colomnIndex; |
|
|
|
} |
|
|
|
|
|
|
|
y -= config.unitSize; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
init(); |
|
|
|
draw(graphList); |
|
|
|
}; |