/dozer/media/javascript/canviz.js
https://bitbucket.org/bbangert/dozer/ · JavaScript · 522 lines · 491 code · 2 blank · 29 comment · 87 complexity · aa61dc00c5c4222c0be94511be6b06b4 MD5 · raw file
- // $Id: canviz.js 390 2007-03-29 10:45:46Z rschmidt $
- var Tokenizer = Class.create();
- Tokenizer.prototype = {
- 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 chars = Number(this.takeChars());
- if ('-' != this.str.charAt(0)) {
- return false;
- }
- var str = this.str.substr(1, chars);
- this.str = this.str.substr(1 + chars).replace(/^\s+/, '');
- return str;
- }
- }
- var Graph = Class.create();
- Graph.prototype = {
- initialize: function(ctx, file, engine) {
- this.maxXdotVersion = 1.2;
- this.systemScale = 4/3;
- this.scale = 1;
- this.padding = 8;
- this.ctx = ctx;
- this.images = new Hash();
- this.numImages = 0;
- this.numImagesFinished = 0;
- if (file) {
- this.load(file, engine);
- }
- },
- setImagePath: function(imagePath) {
- this.imagePath = imagePath;
- },
- load: function(file, engine) {
- $('debug_output').innerHTML = '';
- var url = 'graph.php';
- var params = 'file=' + file;
- if (engine) {
- params += '&engine=' + engine;
- }
- new Ajax.Request(url, {
- method: 'get',
- parameters: params,
- onComplete: this.parse.bind(this)
- });
- },
- parse: function(request) {
- this.xdotversion = false;
- this.commands = new Array();
- this.width = 0;
- this.height = 0;
- this.maxWidth = false;
- this.maxHeight = false;
- this.bbEnlarge = false;
- this.bbScale = 1;
- this.orientation = 'portrait';
- this.bgcolor = '#ffffff';
- this.dashLength = 6;
- this.dotSpacing = 4;
- this.fontName = 'Times New Roman';
- this.fontSize = 14;
- var graph_src = request.responseText;
- var lines = graph_src.split('\n');
- var i = 0;
- var line, lastchar, matches, is_graph, entity, params, param_name, param_value;
- var container_stack = new Array();
- while (i < lines.length) {
- line = lines[i++].replace(/^\s+/, '');
- if ('' != line && '#' != line.substr(0, 1)) {
- while (i < lines.length && ';' != (lastchar = line.substr(line.length - 1, line.length)) && '{' != lastchar && '}' != lastchar) {
- if ('\\' == lastchar) {
- line = line.substr(0, line.length - 1);
- }
- line += lines[i++];
- }
- // debug(line);
- matches = line.match(/^(.*?)\s*{$/);
- if (matches) {
- container_stack.push(matches[1]);
- // debug('begin container ' + container_stack.last());
- } else if ('}' == line) {
- // debug('end container ' + container_stack.last());
- container_stack.pop();
- } else {
- // matches = line.match(/^(".*?[^\\]"|\S+?)\s+\[(.+)\];$/);
- matches = line.match(/^(.*?)\s+\[(.+)\];$/);
- if (matches) {
- is_graph = ('graph' == matches[1]);
- // entity = this.unescape(matches[1]);
- entity = matches[1];
- params = matches[2];
- do {
- matches = params.match(/^(\S+?)=(""|".*?[^\\]"|<(<[^>]+>|[^<>]+?)+>|\S+?)(?:,\s*|$)/);
- if (matches) {
- params = params.substr(matches[0].length);
- param_name = matches[1];
- param_value = this.unescape(matches[2]);
- // debug(param_name + ' ' + param_value);
- if (is_graph && 1 == container_stack.length) {
- switch (param_name) {
- case 'bb':
- var bb = param_value.split(/,/);
- this.width = Number(bb[2]);
- this.height = Number(bb[3]);
- break;
- case 'bgcolor':
- this.bgcolor = this.parseColor(param_value);
- break;
- case 'size':
- var size = param_value.match(/^(\d+|\d*(?:\.\d+)),\s*(\d+|\d*(?:\.\d+))(!?)$/);
- if (size) {
- this.maxWidth = 72 * Number(size[1]);
- this.maxHeight = 72 * Number(size[2]);
- this.bbEnlarge = ('!' == size[3]);
- } else {
- debug('can\'t parse size');
- }
- break;
- case 'orientation':
- if (param_value.match(/^l/i)) {
- this.orientation = 'landscape';
- }
- break;
- case 'rotate':
- if (90 == param_value) {
- this.orientation = 'landscape';
- }
- break;
- case 'xdotversion':
- this.xdotversion = parseFloat(param_value);
- if (this.maxXdotVersion < this.xdotversion) {
- debug('unsupported xdotversion ' + this.xdotversion + '; this script currently supports up to xdotversion ' + this.maxXdotVersion);
- }
- break;
- }
- }
- switch (param_name) {
- case '_draw_':
- case '_ldraw_':
- case '_hdraw_':
- case '_tdraw_':
- case '_hldraw_':
- case '_tldraw_':
- // debug(entity + ': ' + param_value);
- this.commands.push(param_value);
- break;
- }
- }
- } while (matches);
- }
- }
- }
- }
- if (!this.xdotversion) {
- this.xdotversion = 1.0;
- }
- /*
- 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);
- }
- if ('landscape' == this.orientation) {
- var temp = this.width;
- this.width = this.height;
- this.height = temp;
- }
- }
- */
- // debug('done');
- this.draw();
- },
- draw: function(redraw_canvas) {
- if (!redraw_canvas) redraw_canvas = false;
- var width = Math.round(this.scale * this.systemScale * this.width + 2 * this.padding);
- var height = Math.round(this.scale * this.systemScale * this.height + 2 * this.padding);
- if (!redraw_canvas) {
- canvas.width = width;
- canvas.height = height;
- Element.setStyle(canvas, {
- width: width + 'px',
- height: height + 'px'
- });
- Element.setStyle('graph_container', {
- width: width + 'px'
- });
- $('graph_texts').innerHTML = '';
- }
- this.ctx.save();
- this.ctx.lineCap = 'round';
- this.ctx.fillStyle = this.bgcolor;
- this.ctx.fillRect(0, 0, width, height);
- this.ctx.translate(this.padding, this.padding);
- this.ctx.scale(this.scale * this.systemScale, this.scale * this.systemScale);
- this.ctx.lineWidth = 1 / this.systemScale;
- var i, tokens;
- var entity_id = 0;
- var text_divs = '';
- for (var command_index = 0; command_index < this.commands.length; command_index++) {
- var command = this.commands[command_index];
- // debug(command);
- var tokenizer = new Tokenizer(command);
- var token = tokenizer.takeChars();
- if (token) {
- ++entity_id;
- var entity_text_divs = '';
- this.dashStyle = 'solid';
- this.ctx.save();
- while (token) {
- // debug('processing token ' + token);
- switch (token) {
- case 'E': // filled ellipse
- case 'e': // unfilled ellipse
- var filled = ('E' == token);
- var cx = tokenizer.takeNumber();
- var cy = this.height - tokenizer.takeNumber();
- var rx = tokenizer.takeNumber();
- var ry = tokenizer.takeNumber();
- this.render(new Ellipse(cx, cy, rx, ry), filled);
- break;
- case 'P': // filled polygon
- case 'p': // unfilled polygon
- case 'L': // polyline
- var filled = ('P' == token);
- var closed = ('L' != token);
- var num_points = tokenizer.takeNumber();
- tokens = tokenizer.takeNumber(2 * num_points); // points
- var path = new Path();
- for (i = 2; i < 2 * num_points; i += 2) {
- path.addBezier([
- new Point(tokens[i - 2], this.height - tokens[i - 1]),
- new Point(tokens[i], this.height - tokens[i + 1])
- ]);
- }
- if (closed) {
- path.addBezier([
- new Point(tokens[2 * num_points - 2], this.height - tokens[2 * num_points - 1]),
- new Point(tokens[0], this.height - tokens[1])
- ]);
- }
- this.render(path, filled);
- break;
- case 'B': // unfilled b-spline
- case 'b': // filled b-spline
- var filled = ('b' == token);
- var num_points = tokenizer.takeNumber();
- tokens = tokenizer.takeNumber(2 * num_points); // points
- var path = new Path();
- for (i = 2; i < 2 * num_points; i += 6) {
- path.addBezier([
- new Point(tokens[i - 2], this.height - tokens[i - 1]),
- new Point(tokens[i], this.height - tokens[i + 1]),
- new Point(tokens[i + 2], this.height - tokens[i + 3]),
- new Point(tokens[i + 4], this.height - tokens[i + 5])
- ]);
- }
- this.render(path, filled);
- break;
- case 'I': // image
- var x = tokenizer.takeNumber();
- var y = this.height - tokenizer.takeNumber();
- var w = tokenizer.takeNumber();
- var h = tokenizer.takeNumber();
- var src = tokenizer.takeString();
- if (!this.images[src]) {
- y -= h;
- this.images[src] = new GraphImage(this, src, x, y, w, h);
- }
- this.images[src].draw();
- break;
- case 'T': // text
- var x = Math.round(this.scale * this.systemScale * tokenizer.takeNumber() + this.padding);
- var y = Math.round(height - (this.scale * this.systemScale * (tokenizer.takeNumber() + this.bbScale * this.fontSize) + this.padding));
- var text_align = tokenizer.takeNumber();
- var text_width = Math.round(this.scale * this.systemScale * tokenizer.takeNumber());
- var str = tokenizer.takeString();
- if (!redraw_canvas && !str.match(/^\s*$/)) {
- // debug('draw text ' + str + ' ' + x + ' ' + y + ' ' + text_align + ' ' + text_width);
- str = str.escapeHTML();
- do {
- matches = str.match(/ ( +)/);
- if (matches) {
- var spaces = ' ';
- matches[1].length.times(function() {
- spaces += ' ';
- });
- str = str.replace(/ +/, spaces);
- }
- } while (matches);
- entity_text_divs += '<div style="font:' + Math.round(this.fontSize * this.scale * this.systemScale * this.bbScale) + 'px \'' + this.fontName +'\';color:' + this.ctx.strokeStyle + ';';
- switch (text_align) {
- case -1: //left
- entity_text_divs += 'left:' + x + 'px;';
- break;
- case 1: // right
- entity_text_divs += 'text-align:right;right:' + x + 'px;';
- break;
- case 0: // center
- default:
- entity_text_divs += 'text-align:center;left:' + (x - text_width) + 'px;';
- break;
- }
- entity_text_divs += 'top:' + y + 'px;width:' + (2 * text_width) + 'px">' + str + '</div>';
- }
- break;
- case 'C': // set fill color
- case 'c': // set pen color
- var fill = ('C' == token);
- var color = this.parseColor(tokenizer.takeString());
- if (fill) {
- this.ctx.fillStyle = color;
- } else {
- this.ctx.strokeStyle = color;
- }
- break;
- case 'F': // set font
- this.fontSize = tokenizer.takeNumber();
- this.fontName = tokenizer.takeString();
- switch (this.fontName) {
- case 'Times-Roman':
- this.fontName = 'Times New Roman';
- break;
- case 'Courier':
- this.fontName = 'Courier New';
- break;
- case 'Helvetica':
- this.fontName = 'Arial';
- break;
- default:
- // nothing
- }
- // debug('set font ' + this.fontSize + 'pt ' + this.fontName);
- break;
- case 'S': // set style
- var style = tokenizer.takeString();
- switch (style) {
- case 'solid':
- case 'filled':
- // nothing
- break;
- case 'dashed':
- case 'dotted':
- this.dashStyle = style;
- break;
- case 'bold':
- this.ctx.lineWidth = 2 / this.systemScale;
- break;
- default:
- matches = style.match(/^setlinewidth\((.*)\)$/);
- if (matches) {
- this.ctx.lineWidth = Number(matches[1]) / this.systemScale;
- } else {
- debug('unknown style ' + style);
- }
- }
- break;
- default:
- debug('unknown token ' + token);
- return;
- }
- token = tokenizer.takeChars();
- }
- this.ctx.restore();
- if (entity_text_divs) {
- text_divs += '<div id="entity' + entity_id + '">' + entity_text_divs + '</div>';
- }
- }
- };
- this.ctx.restore();
- if (!redraw_canvas) $('graph_texts').innerHTML = text_divs;
- },
- render: function(path, filled) {
- if (filled) {
- this.ctx.beginPath();
- path.draw(this.ctx);
- this.ctx.fill();
- }
- if (this.ctx.fillStyle != this.ctx.strokeStyle || !filled) {
- switch (this.dashStyle) {
- case 'dashed':
- this.ctx.beginPath();
- path.drawDashed(this.ctx, this.dashLength);
- break;
- case 'dotted':
- var oldLineWidth = this.ctx.lineWidth;
- this.ctx.lineWidth *= 2;
- this.ctx.beginPath();
- path.drawDotted(this.ctx, this.dotSpacing);
- break;
- case 'solid':
- default:
- if (!filled) {
- this.ctx.beginPath();
- path.draw(this.ctx);
- }
- }
- this.ctx.stroke();
- if (oldLineWidth) this.ctx.lineWidth = oldLineWidth;
- }
- },
- unescape: function(str) {
- var matches = str.match(/^"(.*)"$/);
- if (matches) {
- return matches[1].replace(/\\"/g, '"');
- } else {
- return str;
- }
- },
- parseColor: function(color) {
- if (gvcolors[color]) { // named color
- return 'rgb(' + gvcolors[color][0] + ',' + gvcolors[color][1] + ',' + gvcolors[color][2] + ')';
- } else {
- var matches = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
- if (matches) { // rgba
- return 'rgba(' + parseInt(matches[1], 16) + ',' + parseInt(matches[2], 16) + ',' + parseInt(matches[3], 16) + ',' + (parseInt(matches[4], 16) / 255) + ')';
- } else {
- matches = color.match(/(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)/);
- if (matches) { // hsv
- return this.hsvToRgbColor(matches[1], matches[2], matches[3]);
- } else if (color.match(/^#[0-9a-f]{6}$/i)) {
- return color;
- }
- }
- }
- debug('unknown color ' + color);
- return '#000000';
- },
- 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) + ')';
- }
- }
- var GraphImage = Class.create();
- GraphImage.prototype = {
- initialize: function(graph, src, x, y, w, h) {
- this.graph = graph;
- ++this.graph.numImages;
- this.src = this.graph.imagePath + '/' + src;
- this.x = x;
- this.y = y;
- this.w = w;
- this.h = h;
- this.loaded = false;
- this.img = new Image();
- this.img.onload = this.succeeded.bind(this);
- this.img.onerror = this.finished.bind(this);
- this.img.onabort = this.finished.bind(this);
- this.img.src = this.src;
- },
- succeeded: function() {
- this.loaded = true;
- this.finished();
- },
- finished: function() {
- ++this.graph.numImagesFinished;
- if (this.graph.numImages == this.graph.numImagesFinished) {
- this.graph.draw(true);
- }
- },
- draw: function() {
- if (this.loaded) {
- this.graph.ctx.drawImage(this.img, this.x, this.y, this.w, this.h);
- }
- }
- }
- function debug(str) {
- $('debug_output').innerHTML += '»' + String(str).escapeHTML() + '«<br />';
- }