/*
* This file is part of Canviz. See http://www.canviz.org/
* $Id: canviz.js 265 2009-05-19 13:35:13Z ryandesign.com $
*/
var CanvizTokenizer = Class.create({
initialize: function(str) {
this.str = str;
},
takeChars: function(num) {
if (!num) {
num = 1;
}
var tokens = new Array();
while (num--) {
var matches = this.str.match(/^(\S+)\s*/);
if (matches) {
this.str = this.str.substr(matches[0].length);
tokens.push(matches[1]);
} else {
tokens.push(false);
}
}
if (1 == tokens.length) {
return tokens[0];
} else {
return tokens;
}
},
takeNumber: function(num) {
if (!num) {
num = 1;
}
if (1 == num) {
return Number(this.takeChars());
} else {
var tokens = this.takeChars(num);
while (num--) {
tokens[num] = Number(tokens[num]);
}
return tokens;
}
},
takeString: function() {
var byteCount = Number(this.takeChars()), charCount = 0, charCode;
if ('-' != this.str.charAt(0)) {
return false;
}
while (0 < byteCount) {
++charCount;
charCode = this.str.charCodeAt(charCount);
if (0x80 > charCode) {
--byteCount;
} else if (0x800 > charCode) {
byteCount -= 2;
} else {
byteCount -= 3;
}
}
var str = this.str.substr(1, charCount);
this.str = this.str.substr(1 + charCount).replace(/^\s+/, '');
return str;
}
});
var CanvizEntity = Class.create({
initialize: function(defaultAttrHashName, name, canviz, rootGraph, parentGraph, immediateGraph) {
this.defaultAttrHashName = defaultAttrHashName;
this.name = name;
this.canviz = canviz;
this.rootGraph = rootGraph;
this.parentGraph = parentGraph;
this.immediateGraph = immediateGraph;
this.attrs = $H();
this.drawAttrs = $H();
},
initBB: function() {
var matches = this.getAttr('pos').match(/([0-9.]+),([0-9.]+)/);
var x = Math.round(matches[1]);
var y = Math.round(this.canviz.height - matches[2]);
this.bbRect = new Rect(x, y, x, y);
},
getAttr: function(attrName, escString) {
if (Object.isUndefined(escString)) escString = false;
var attrValue = this.attrs.get(attrName);
if (Object.isUndefined(attrValue)) {
var graph = this.parentGraph;
while (!Object.isUndefined(graph)) {
attrValue = graph[this.defaultAttrHashName].get(attrName);
if (Object.isUndefined(attrValue)) {
graph = graph.parentGraph;
} else {
break;
}
}
}
if (attrValue && escString) {
attrValue = attrValue.replace(this.escStringMatchRe, function(match, p1) {
switch (p1) {
case 'N': // fall through
case 'E': return this.name;
case 'T': return this.tailNode;
case 'H': return this.headNode;
case 'G': return this.immediateGraph.name;
case 'L': return this.getAttr('label', true);
}
return match;
}.bind(this));
}
return attrValue;
},
draw: function(ctx, ctxScale, redrawCanvasOnly) {
var i, tokens, fillColor, strokeColor, label;
var urls, href_index, href_count;
if (!redrawCanvasOnly) {
this.initBB();
var bbDiv = new Element('div');
this.canviz.elements.appendChild(bbDiv);
}
label = this.getAttr('label', true);
if ( label && label.match('
') ) {
var h;
// debug('Got a table');
urls = new Array();
var hrefl = label.split('href="');
for(h=1; h this.versionCompare(this.maxXdotVersion, attrHash.get('xdotversion'))) {
debug('unsupported xdotversion ' + attrHash.get('xdotversion') + '; this script currently supports up to xdotversion ' + this.maxXdotVersion);
}
break;
}
}
} else {
debug('can\'t read attributes for entity ' + entityName + ' from ' + attrs);
}
} while (matches);
}
}
}
}
/*
if (this.maxWidth && this.maxHeight) {
if (this.width > this.maxWidth || this.height > this.maxHeight || this.bbEnlarge) {
this.bbScale = Math.min(this.maxWidth / this.width, this.maxHeight / this.height);
this.width = Math.round(this.width * this.bbScale);
this.height = Math.round(this.height * this.bbScale);
}
}
*/
// debug('done');
this.draw();
},
draw: function(redrawCanvasOnly) {
if (Object.isUndefined(redrawCanvasOnly)) redrawCanvasOnly = false;
var ctxScale = this.scale * this.dpi / 72;
var width = Math.round(ctxScale * this.width + 2 * this.padding);
var height = Math.round(ctxScale * this.height + 2 * this.padding);
if (!redrawCanvasOnly) {
this.canvas.width = width;
this.canvas.height = height;
this.canvas.setStyle({
width: width + 'px',
height: height + 'px'
});
this.container.setStyle({
width: width + 'px'
});
while (this.elements.firstChild) {
this.elements.removeChild(this.elements.firstChild);
}
}
this.ctx.save();
this.ctx.lineCap = 'round';
this.ctx.fillStyle = this.bgcolor.canvasColor;
this.ctx.fillRect(0, 0, width, height);
this.ctx.translate(this.padding, this.padding);
this.ctx.scale(ctxScale, ctxScale);
this.graphs[0].draw(this.ctx, ctxScale, redrawCanvasOnly);
this.ctx.restore();
},
drawPath: function(ctx, path, filled, dashStyle) {
if (filled) {
ctx.beginPath();
path.makePath(ctx);
ctx.fill();
}
if (ctx.fillStyle != ctx.strokeStyle || !filled) {
switch (dashStyle) {
case 'dashed':
ctx.beginPath();
path.makeDashedPath(ctx, this.dashLength);
break;
case 'dotted':
var oldLineWidth = ctx.lineWidth;
ctx.lineWidth *= 2;
ctx.beginPath();
path.makeDottedPath(ctx, this.dotSpacing);
break;
case 'solid':
default:
if (!filled) {
ctx.beginPath();
path.makePath(ctx);
}
}
ctx.stroke();
if (oldLineWidth) ctx.lineWidth = oldLineWidth;
}
},
unescape: function(str) {
var matches = str.match(/^"(.*)"$/);
if (matches) {
return matches[1].replace(/\\"/g, '"');
} else {
return str;
}
},
parseHexColor: function(color) {
var matches = color.match(/^#([0-9a-f]{2})\s*([0-9a-f]{2})\s*([0-9a-f]{2})\s*([0-9a-f]{2})?$/i);
if (matches) {
var canvasColor, textColor = '#' + matches[1] + matches[2] + matches[3], opacity = 1;
if (matches[4]) { // rgba
opacity = parseInt(matches[4], 16) / 255;
canvasColor = 'rgba(' + parseInt(matches[1], 16) + ',' + parseInt(matches[2], 16) + ',' + parseInt(matches[3], 16) + ',' + opacity + ')';
} else { // rgb
canvasColor = textColor;
}
}
return {canvasColor: canvasColor, textColor: textColor, opacity: opacity};
},
hsvToRgbColor: function(h, s, v) {
var i, f, p, q, t, r, g, b;
h *= 360;
i = Math.floor(h / 60) % 6;
f = h / 60 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
switch (i) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
return 'rgb(' + Math.round(255 * r) + ',' + Math.round(255 * g) + ',' + Math.round(255 * b) + ')';
},
versionCompare: function(a, b) {
a = a.split('.');
b = b.split('.');
var a1, b1;
while (a.length || b.length) {
a1 = a.length ? a.shift() : 0;
b1 = b.length ? b.shift() : 0;
if (a1 < b1) return -1;
if (a1 > b1) return 1;
}
return 0;
},
// an alphanumeric string or a number or a double-quoted string or an HTML string
idMatch: '([a-zA-Z\u0080-\uFFFF_][0-9a-zA-Z\u0080-\uFFFF_]*|-?(?:\\.\\d+|\\d+(?:\\.\\d*)?)|"(?:\\\\"|[^"])*"|<(?:<[^>]*>|[^<>]+?)+>)'
});
Object.extend(Canviz.prototype, {
// ID or ID:port or ID:compassPoint or ID:port:compassPoint
nodeIdMatch: Canviz.prototype.idMatch + '(?::' + Canviz.prototype.idMatch + ')?(?::' + Canviz.prototype.idMatch + ')?'
});
Object.extend(Canviz.prototype, {
graphMatchRe: new RegExp('^(strict\\s+)?(graph|digraph)(?:\\s+' + Canviz.prototype.idMatch + ')?\\s*{$', 'i'),
subgraphMatchRe: new RegExp('^(?:subgraph\\s+)?' + Canviz.prototype.idMatch + '?\\s*{$', 'i'),
nodeMatchRe: new RegExp('^(' + Canviz.prototype.nodeIdMatch + ')\\s+\\[(.+)\\];$'),
edgeMatchRe: new RegExp('^(' + Canviz.prototype.nodeIdMatch + '\\s*-[->]\\s*' + Canviz.prototype.nodeIdMatch + ')\\s+\\[(.+)\\];$'),
attrMatchRe: new RegExp('^' + Canviz.prototype.idMatch + '=' + Canviz.prototype.idMatch + '(?:[,\\s]+|$)')
});
var CanvizImage = Class.create({
initialize: function(canviz, src) {
this.canviz = canviz;
++this.canviz.numImages;
this.finished = this.loaded = false;
this.img = new Image();
this.img.onload = this.onLoad.bind(this);
this.img.onerror = this.onFinish.bind(this);
this.img.onabort = this.onFinish.bind(this);
this.img.src = this.canviz.imagePath + src;
},
onLoad: function() {
this.loaded = true;
this.onFinish();
},
onFinish: function() {
this.finished = true;
++this.canviz.numImagesFinished;
if (this.canviz.numImages == this.canviz.numImagesFinished) {
this.canviz.draw(true);
}
},
draw: function(ctx, l, t, w, h) {
if (this.finished) {
if (this.loaded) {
ctx.drawImage(this.img, l, t, w, h);
} else {
debug('can\'t load image ' + this.img.src);
this.drawBrokenImage(ctx, l, t, w, h);
}
}
},
drawBrokenImage: function(ctx, l, t, w, h) {
ctx.save();
ctx.beginPath();
new Rect(l, t, l + w, t + w).draw(ctx);
ctx.moveTo(l, t);
ctx.lineTo(l + w, t + w);
ctx.moveTo(l + w, t);
ctx.lineTo(l, t + h);
ctx.strokeStyle = '#f00';
ctx.lineWidth = 1;
ctx.stroke();
ctx.restore();
}
});
function debug(str, escape) {
str = String(str);
if (Object.isUndefined(escape)) {
escape = true;
}
if (escape) {
str = str.escapeHTML();
}
$('debug_output').innerHTML += '»' + str + '«
';
}