PageRenderTime 55ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/src/graphx/Viz.hx

http://graphx.googlecode.com/
Haxe | 906 lines | 802 code | 50 blank | 54 comment | 92 complexity | edb05e3d520b3f5e0c456b30dcd8c1f4 MD5 | raw file
  1. package graphx;
  2. import flash.display.Graphics;
  3. import flash.display.LoaderInfo;
  4. import flash.display.Sprite;
  5. import flash.display.Stage;
  6. import flash.display.StageScaleMode;
  7. import flash.events.Event;
  8. import flash.Lib;
  9. import flash.net.URLLoader;
  10. import flash.net.URLRequest;
  11. import flash.text.TextField;
  12. import flash.text.TextFieldAutoSize;
  13. import flash.text.TextFormat;
  14. import graphx.Path;
  15. class VizTokenizer {
  16. var str:String;
  17. public function new(str:String) {
  18. this.str = str;
  19. }
  20. public function takeChar() : String {
  21. var r = ~/^(\S+)\s*/;
  22. var matches = r.match(this.str);
  23. if (matches) {
  24. this.str = this.str.substr( r.matched(0).length );
  25. return r.matched(1);
  26. } else
  27. return '';
  28. }
  29. public function takeNumbers(?num:Int = 1) : Array<Float> {
  30. var tokens = new Array<Float>();
  31. for (i in 0...num) {
  32. var char = takeChar();
  33. if (char != '')
  34. tokens.push( Std.parseFloat(char) );
  35. }
  36. return tokens;
  37. }
  38. public function takeNumber() : Float {
  39. return Std.parseFloat( takeChar() );
  40. }
  41. public function takeString() : String {
  42. var byteCount = Std.parseInt(this.takeChar());
  43. var charCount = 0;
  44. var charCode = -1;
  45. if ('-' != this.str.charAt(0)) {
  46. return '';
  47. }
  48. while (0 < byteCount) {
  49. ++charCount;
  50. charCode = this.str.charCodeAt(charCount);
  51. if (0x80 > charCode) {
  52. --byteCount;
  53. } else if (0x800 > charCode) {
  54. byteCount -= 2;
  55. } else {
  56. byteCount -= 3;
  57. }
  58. }
  59. var s = this.str.substr(1, charCount);
  60. this.str = ~/^\s+/.replace(this.str.substr(1 + charCount), '');
  61. return s;
  62. }
  63. }
  64. class VizEntity {
  65. public var defaultAttrHashName:String;
  66. public var name:String;
  67. public var viz:Viz;
  68. public var rootGraph:VizGraph;
  69. public var parentGraph:VizGraph;
  70. public var immediateGraph:VizGraph;
  71. public var attrs:Hash<String>;
  72. public var drawAttrs:Hash<String>;
  73. public var bbRect:Rect;
  74. public var escStringMatchRe:EReg;
  75. public function new(defaultAttrHashName, name, viz, ?rootGraph, ?parentGraph, ?immediateGraph) {
  76. this.defaultAttrHashName = defaultAttrHashName;
  77. this.name = name;
  78. this.viz = viz;
  79. this.rootGraph = rootGraph;
  80. this.parentGraph = parentGraph;
  81. this.immediateGraph = immediateGraph;
  82. attrs = new Hash();
  83. drawAttrs = new Hash();
  84. }
  85. public function initBB() {
  86. var r = ~/([0-9.]+),([0-9.]+)/;
  87. var matches = r.match(this.getAttr('pos'));
  88. if (matches) {
  89. var x = Math.round( Std.parseFloat(r.matched(1)) );
  90. var y = Math.round( this.viz.height - Std.parseFloat(r.matched(2)) );
  91. this.bbRect = new Rect(x, y, x, y);
  92. }
  93. }
  94. function getAttr(attrName:String, ?escString:Bool=false) {
  95. var attrValue = null;
  96. if (!this.attrs.exists(attrName)) {
  97. var graph = this.parentGraph;
  98. while (graph != null) {
  99. if (cast( Reflect.field(graph, this.defaultAttrHashName) ).exists(attrName)) {
  100. attrValue = cast( Reflect.field(graph, this.defaultAttrHashName) ).get(attrName);
  101. graph = graph.parentGraph;
  102. } else {
  103. break;
  104. }
  105. }
  106. } else
  107. attrValue = this.attrs.get(attrName);
  108. if (attrValue != null && escString) {
  109. var me = this;
  110. attrValue = this.escStringMatchRe.customReplace( attrValue, function( r:EReg ) {
  111. return switch( r.matched(1) ) {
  112. case 'N', 'E': me.name;
  113. case 'T': cast(me,VizEdge).tailNode;
  114. case 'H': cast(me,VizEdge).headNode;
  115. case 'G': me.immediateGraph.name;
  116. case 'L': me.getAttr('label', true);
  117. }
  118. });
  119. }
  120. return attrValue;
  121. }
  122. public function draw(ctx:Graphics, ctxScale:Float, redrawCanvasOnly:Bool) {
  123. var i, tokens, fillColor;
  124. var strokeColor = {color:this.viz.lineColor, alpha:1.0};
  125. var fontSize:Int = 12;
  126. var fontFamily:String = 'Times New Roman';
  127. var path:Path = null;
  128. var filled = false;
  129. var bbDiv:Sprite = null;
  130. if (!redrawCanvasOnly) {
  131. this.initBB();
  132. }
  133. for (drawAttr in drawAttrs) {
  134. var command = drawAttr;
  135. var tokenizer = new VizTokenizer(command);
  136. var token = tokenizer.takeChar();
  137. if (token != null && token != '') {
  138. var dashStyle = 'solid';
  139. while (token != null && token != '') {
  140. switch (token) {
  141. case 'E', // filled ellipse
  142. 'e': // unfilled ellipse
  143. filled = ('E' == token);
  144. var cx = tokenizer.takeNumber();
  145. var cy = this.viz.height - tokenizer.takeNumber();
  146. var rx = tokenizer.takeNumber();
  147. var ry = tokenizer.takeNumber();
  148. path = new Ellipse(cx, cy, rx, ry);
  149. case 'P', // filled polygon
  150. 'p', // unfilled polygon
  151. 'L': // polyline
  152. filled = ('P' == token);
  153. var closed = ('L' != token);
  154. var numPoints = Std.int(tokenizer.takeNumber());
  155. tokens = tokenizer.takeNumbers(2 * numPoints); // points
  156. path = new Path();
  157. for (i in 1...numPoints) {
  158. var indx = i * 2;
  159. path.addBezier([
  160. new Point(tokens[indx - 2], this.viz.height - tokens[indx - 1]),
  161. new Point(tokens[indx], this.viz.height - tokens[indx + 1])
  162. ]);
  163. }
  164. if (closed) {
  165. path.addBezier([
  166. new Point(tokens[2 * numPoints - 2], this.viz.height - tokens[2 * numPoints - 1]),
  167. new Point(tokens[0], this.viz.height - tokens[1])
  168. ]);
  169. }
  170. case 'B', // unfilled b-spline
  171. 'b': // filled b-spline
  172. filled = ('b' == token);
  173. var numPoints = Std.int(tokenizer.takeNumber());
  174. tokens = tokenizer.takeNumbers(2 * numPoints); // points
  175. path = new Path();
  176. var i = 2;
  177. while (i < 2 * numPoints) {
  178. path.addBezier([
  179. new Point(tokens[i - 2], this.viz.height - tokens[i - 1]),
  180. new Point(tokens[i], this.viz.height - tokens[i + 1]),
  181. new Point(tokens[i + 2], this.viz.height - tokens[i + 3]),
  182. new Point(tokens[i + 4], this.viz.height - tokens[i + 5])
  183. ]);
  184. i += 6;
  185. }
  186. case 'I': // image
  187. var l = tokenizer.takeNumber();
  188. var b = this.viz.height - tokenizer.takeNumber();
  189. var w = tokenizer.takeNumber();
  190. var h = tokenizer.takeNumber();
  191. var src = tokenizer.takeString();
  192. if (!this.viz.images.exists(src)) {
  193. this.viz.images.set(src, new VizImage(this.viz, src));
  194. }
  195. this.viz.images.get(src).draw(ctx, l, b - h, w, h);
  196. case 'T': // text
  197. var l = tokenizer.takeNumber();
  198. var t = this.viz.height - tokenizer.takeNumber();
  199. var textAlign = tokenizer.takeNumber();
  200. var textWidth = Math.round(ctxScale * tokenizer.takeNumber());
  201. var str = tokenizer.takeString();
  202. if (!redrawCanvasOnly && !(~/^\s*$/.match(str)) ) {
  203. str = StringTools.htmlEscape(str);
  204. var matches:Bool;
  205. var r:EReg;
  206. do {
  207. r = ~/ ( +)/;
  208. matches = r.match(str);
  209. if (matches) {
  210. var spaces = ' ';
  211. for (i in 0...r.matched(1).length) {
  212. spaces += '&nbsp;';
  213. }
  214. str = ~/ +/.replace(str, spaces);
  215. }
  216. } while (matches);
  217. // TODO: add href etc
  218. /*
  219. var text;
  220. var url = this.getAttr('URL', true);
  221. var href = url != null ? url : this.getAttr('href', true);
  222. if (href != null && href != '') {
  223. var tmpTarget = this.getAttr('target', true);
  224. var target = tmpTarget != null ? tmpTarget : '_self';
  225. var tmpTooltip = this.getAttr('tooltip', true);
  226. var tooltip = tmpTooltip != null ? tmpTooltip : this.getAttr('label', true);
  227. // debug(this.name + ', href ' + href + ', target ' + target + ', tooltip ' + tooltip);
  228. text = new Element('a', {href: href, target: target, title: tooltip});
  229. ['onclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmousemove', 'onmouseout'].each(function(attrName) {
  230. var attrValue = this.getAttr(attrName, true);
  231. if (attrValue != null) {
  232. text.writeAttribute(attrName, attrValue);
  233. }
  234. }.bind(this));
  235. text.setStyle({
  236. textDecoration: 'none'
  237. });
  238. } else {
  239. //text = new Element('span');
  240. }
  241. */
  242. var preText = '<P ALIGN="CENTER"><FONT';
  243. preText += ' SIZE="' + Std.string( fontSize ) + '"';
  244. preText += ' COLOR="#' + StringTools.hex(strokeColor.color, 6) + '"';
  245. if (fontFamily != null && fontFamily != '')
  246. preText += ' FACE="' + fontFamily + '"';
  247. preText += '>';
  248. var postText = '</FONT></P>';
  249. // TODO: improve positioning
  250. /*
  251. text.update(str);
  252. text.setStyle({
  253. fontSize: Math.round(fontSize * ctxScale * this.viz.bbScale) + 'px',
  254. fontFamily: fontFamily,
  255. color: strokeColor.textColor,
  256. position: 'absolute',
  257. textAlign: (-1 == textAlign) ? 'left' : (1 == textAlign) ? 'right' : 'center',
  258. left: (l - (1 + textAlign) * textWidth) + 'px',
  259. top: t + 'px',
  260. width: (2 * textWidth) + 'px'
  261. });
  262. if (1 != strokeColor.opacity) text.setOpacity(strokeColor.opacity);
  263. this.viz.elements.appendChild(text);
  264. */
  265. var tf:TextField = new TextField();
  266. tf.autoSize = TextFieldAutoSize.CENTER;
  267. tf.htmlText = preText + str + postText;
  268. tf.x = l - tf.width/2;
  269. tf.y = t - tf.height*5/6;
  270. this.viz.canvas.addChild(tf);
  271. }
  272. case 'C', // set fill color
  273. 'c': // set pen color
  274. var fill = ('C' == token);
  275. var color = this.parseColorFlash(tokenizer.takeString());
  276. if (fill) {
  277. fillColor = color;
  278. ctx.beginFill(color.color, color.alpha);
  279. this.viz.fillStyle = color.color;
  280. } else {
  281. strokeColor = color;
  282. ctx.lineStyle(this.viz.lineWidth, color.color, color.alpha);
  283. this.viz.strokeStyle = color.color;
  284. }
  285. case 'F': // set font
  286. fontSize = Std.int(tokenizer.takeNumber());
  287. fontFamily = tokenizer.takeString();
  288. switch (fontFamily) {
  289. case 'Times-Roman':
  290. fontFamily = 'Times New Roman';
  291. case 'Courier':
  292. fontFamily = 'Courier New';
  293. case 'Helvetica':
  294. fontFamily = 'Arial';
  295. default:
  296. // nothing
  297. }
  298. case 'S': // set style
  299. var style = tokenizer.takeString();
  300. switch (style) {
  301. case 'solid', 'filled': // nothing
  302. case 'dashed', 'dotted':
  303. dashStyle = style;
  304. case 'bold':
  305. this.viz.lineWidth = 2;
  306. ctx.lineStyle(this.viz.lineWidth, this.viz.lineColor, this.viz.lineAlpha);
  307. default:
  308. var r = ~/^setlinewidth\((.*)\)$/;
  309. var matches = r.match(style);
  310. if (matches) {
  311. this.viz.lineWidth = Std.parseFloat(r.matched(1));
  312. ctx.lineStyle(this.viz.lineWidth, this.viz.lineColor, this.viz.lineAlpha);
  313. } else {
  314. //Logger.log('unknown style ' + style);
  315. }
  316. }
  317. default:
  318. //Logger.log('unknown token ' + token);
  319. return;
  320. }
  321. if (path != null) {
  322. this.viz.drawPath(ctx, path, filled, dashStyle);
  323. if (!redrawCanvasOnly) this.bbRect.expandToInclude(path.getBB());
  324. path = null;
  325. }
  326. token = tokenizer.takeChar();
  327. }
  328. this.viz.lineWidth = 1.0;
  329. }
  330. }
  331. }
  332. public function parseColorFlash(color:String) {
  333. var c = color.split("#").join("");
  334. var col = Std.parseInt('0x' + c.substr(0, 6));
  335. var alpha = 1.0;
  336. if (c.length > 6)
  337. alpha = Std.parseInt('0x' + c.substr( -2)) / 255.0;
  338. return { color:col, alpha:alpha };
  339. }
  340. public function parseColor(color:String) {
  341. var parsedColor = {opacity: 1.0, canvasColor: '', textColor: ''};
  342. // rgb/rgba
  343. if (~/^#(?:[0-9a-f]{2}\s*){3,4}$/i.match(color)) {
  344. return this.viz.parseHexColor(color);
  345. }
  346. // hsv
  347. var hsvRe = ~/^(\d+(?:\.\d+)?)[\s,]+(\d+(?:\.\d+)?)[\s,]+(\d+(?:\.\d+)?)$/;
  348. var matches = hsvRe.match(color);
  349. if (matches) {
  350. parsedColor.canvasColor = parsedColor.textColor = this.viz.hsvToRgbColor(
  351. Std.parseInt(hsvRe.matched(1)), Std.parseInt(hsvRe.matched(2)), Std.parseInt(hsvRe.matched(3))
  352. );
  353. return parsedColor;
  354. }
  355. // named color
  356. var colAtt = this.getAttr('colorscheme');
  357. var colorScheme = colAtt != null ? colAtt : 'X11';
  358. var colorName = color;
  359. var namedColRe = ~/^\/(.*)\/(.*)$/;
  360. matches = namedColRe.match(color);
  361. if (matches) {
  362. if (namedColRe.matched(1) != null && namedColRe.matched(1) != '') {
  363. colorScheme = namedColRe.matched(1);
  364. }
  365. colorName = namedColRe.matched(2);
  366. } else {
  367. var r = ~/^\/(.*)$/;
  368. matches = r.match(color);
  369. if (matches) {
  370. colorScheme = 'X11';
  371. colorName = r.matched(1);
  372. }
  373. }
  374. colorName = colorName.toLowerCase();
  375. var colorData = '';
  376. var colorSchemeData = null;
  377. var colorSchemeName = colorScheme.toLowerCase();
  378. if (this.viz.colors.exists(colorSchemeName)) {
  379. colorSchemeData = this.viz.colors.get(colorSchemeName);
  380. if (colorSchemeData.exists(colorName)) {
  381. colorData = colorSchemeData.get(colorName);
  382. return this.viz.parseHexColor('#' + colorData);
  383. }
  384. }
  385. if (this.viz.colors.get('fallback').exists(colorName)) {
  386. colorData = this.viz.colors.get('fallback').get(colorName);
  387. return this.viz.parseHexColor('#' + colorData);
  388. }
  389. if (colorSchemeData == null) {
  390. //Logger.log('unknown color scheme ' + colorScheme);
  391. }
  392. // unknown
  393. //Logger.log('unknown color ' + color + '; color scheme is ' + colorScheme);
  394. parsedColor.canvasColor = parsedColor.textColor = '#000000';
  395. return parsedColor;
  396. }
  397. }
  398. class VizNode extends VizEntity {
  399. public function new(name, viz, rootGraph, parentGraph) {
  400. super('nodeAttrs', name, viz, rootGraph, parentGraph, parentGraph);
  401. escStringMatchRe = new EReg('([NGL])', 'g');
  402. }
  403. }
  404. class VizEdge extends VizEntity {
  405. public var tailNode:String;
  406. public var headNode:String;
  407. public function new(name:String, viz:Viz, rootGraph, parentGraph, tailNode, headNode) {
  408. super('edgeAttrs', name, viz, rootGraph, parentGraph, parentGraph);
  409. this.tailNode = tailNode;
  410. this.headNode = headNode;
  411. escStringMatchRe = new EReg('([EGTHL])', 'g');
  412. }
  413. }
  414. class VizGraph extends VizEntity {
  415. public var subgraphs:Array<VizGraph>;
  416. public var strict:Bool;
  417. public var type:String;
  418. public var nodes:Array<VizNode>;
  419. public var edges:Array<VizEdge>;
  420. public var nodeAttrs:Hash<String>;
  421. public var edgeAttrs:Hash<String>;
  422. public function new(name:String, viz:Viz, ?rootGraph, ?parentGraph) {
  423. super('attrs', name, viz, rootGraph, parentGraph, this);
  424. nodes = [];
  425. edges = [];
  426. subgraphs = [];
  427. nodeAttrs = new Hash();
  428. edgeAttrs = new Hash();
  429. escStringMatchRe = new EReg('([GL])', 'g');
  430. }
  431. override public function initBB() {
  432. var coords = this.getAttr('bb').split(',');
  433. this.bbRect = new Rect(
  434. Std.parseFloat(coords[0]), this.viz.height - Std.parseFloat(coords[1]),
  435. Std.parseFloat(coords[2]), this.viz.height - Std.parseFloat(coords[3])
  436. );
  437. }
  438. override public function draw(ctx:Graphics, ctxScale:Float, redrawCanvasOnly:Bool) {
  439. super.draw(ctx, ctxScale, redrawCanvasOnly);
  440. for (node in nodes)
  441. node.draw(ctx, ctxScale, redrawCanvasOnly);
  442. for (subgraph in subgraphs)
  443. subgraph.draw(ctx, ctxScale, redrawCanvasOnly);
  444. for (edge in edges)
  445. edge.draw(ctx, ctxScale, redrawCanvasOnly);
  446. }
  447. }
  448. class Viz {
  449. static var idMatch:String = '([a-zA-Z\x80-\xffff_][0-9a-zA-Z\x80-\xffff_]*|-?(?:\\.\\d+|\\d+(?:\\.\\d*)?)|"(?:\\\\"|[^"])*"|<(?:<[^>]*>|[^<>]+?)+>)';
  450. static var nodeIdMatch:String = idMatch + '(?::' + idMatch + ')?(?::' + idMatch + ')?';
  451. static var graphMatchRe:EReg = new EReg('^(strict\\s+)?(graph|digraph)(?:\\s+' + idMatch + ')?\\s*{$', 'i');
  452. static var subgraphMatchRe:EReg = new EReg('^(?:subgraph\\s+)?' + idMatch + '?\\s*{$', 'i');
  453. static var nodeMatchRe:EReg = new EReg('^(' + nodeIdMatch + ')\\s+\\[(.+)\\];$','');
  454. static var edgeMatchRe:EReg = new EReg('^(' + nodeIdMatch + '\\s*-[->]\\s*' + nodeIdMatch + ')\\s+\\[(.+)\\];$','');
  455. static var attrMatchRe:EReg = new EReg('^' + idMatch + '=' + idMatch + '(?:[,\\s]+|$)', '');
  456. public static function main() {
  457. new Viz();
  458. }
  459. public var maxXdotVersion:String;
  460. public var imagePath:String;
  461. public var scale:Float;
  462. public var padding:Float;
  463. public var dashLength:Float;
  464. public var dotSpacing:Float;
  465. public var images:Hash<VizImage>;
  466. public var numImages:Int;
  467. public var numImagesFinished:Int;
  468. public var canvas:Sprite;
  469. public var colors:Hash<Hash<String>>;
  470. public var loader:URLLoader;
  471. public var lineWidth:Float;
  472. public var lineColor:Int;
  473. public var lineAlpha:Float;
  474. public var dashStyle:String;
  475. public var fillStyle:Int;
  476. public var strokeStyle:Int;
  477. public var lineCap:flash.display.CapsStyle;
  478. public function new() {
  479. maxXdotVersion = '1.2';
  480. colors = new Hash();
  481. var fallBackHash = new Hash<String>();
  482. fallBackHash.set('black', '000000');
  483. fallBackHash.set('lightgrey', 'd3d3d3');
  484. fallBackHash.set('white', 'ffffff');
  485. colors.set('fallback', fallBackHash);
  486. imagePath = "";
  487. scale = 1.0;
  488. padding = 8.0;
  489. dashLength = 6.0;
  490. dotSpacing = 4.0;
  491. images = new Hash();
  492. numImages = 0;
  493. numImagesFinished = 0;
  494. lineWidth = 1.0;
  495. lineColor = 0xffffff;
  496. lineAlpha = 1.0;
  497. fillStyle = strokeStyle = 0xffffff;
  498. dashStyle = 'solid';
  499. lineCap = flash.display.CapsStyle.ROUND;
  500. Lib.current.stage.scaleMode = StageScaleMode.NO_SCALE;
  501. canvas = new Sprite();
  502. Lib.current.addChild(canvas);
  503. var inputUrl = Reflect.field( Reflect.field( Reflect.field(Lib.current, "loaderInfo"), "parameters" ), "input");
  504. loader = new URLLoader();
  505. loader.addEventListener(Event.COMPLETE, loaded);
  506. //var url = "../../bin/cluster.gv.xdot";
  507. //loader.load(new URLRequest(url));
  508. loader.load(new URLRequest(inputUrl));
  509. }
  510. function loaded( e : Event ) {
  511. parse( e.target.data );
  512. }
  513. // parse props
  514. public var graphs:Array<VizGraph>;
  515. public var width:Float;
  516. public var height:Float;
  517. public var maxWidth:Float;
  518. public var maxHeight:Float;
  519. public var bbEnlarge:Bool;
  520. public var bbScale:Float;
  521. public var dpi:Float;
  522. public var bgcolor:{color:Int,alpha:Float};
  523. public function parse(xdot:String) {
  524. graphs = [];
  525. width = 0.0;
  526. height = 0.0;
  527. maxWidth = -1.0;
  528. maxHeight = -1.0;
  529. bbEnlarge = false;
  530. bbScale = 1.0;
  531. dpi = 96;
  532. bgcolor = {
  533. color: 0xffffff,
  534. alpha:1.0
  535. };
  536. var xdotRe:EReg = ~/\r?\n/;
  537. //var lines = xdotRe.split(xdot);
  538. var lines = xdot.split("\r").join("").split("\n");
  539. var i = 0;
  540. var entity:VizEntity;
  541. var rootGraph:VizGraph = null;
  542. var attrs:String = "";
  543. var drawAttrHash = new Hash<String>();
  544. var attrHash = new Hash<String>();
  545. var isGraph = false;
  546. var entityName = "";
  547. var line, lastChar, matches, attrName, attrValue;
  548. var containers = new Array<VizGraph>();
  549. while (i < lines.length) {
  550. var r : EReg = ~/^\s+/;
  551. line = r.replace(lines[i++], '');
  552. if ('' != line && '#' != line.substr(0, 1)) {
  553. while (i < lines.length && ';' != (lastChar = line.substr(-1)) && '{' != lastChar && '}' != lastChar) {
  554. if ('\\' == lastChar) {
  555. line = line.substr(0, line.length - 1);
  556. }
  557. line += lines[i++];
  558. }
  559. if (0 == containers.length) {
  560. matches = graphMatchRe.match(line);
  561. if (matches) {
  562. rootGraph = new VizGraph(graphMatchRe.matched(3), this);
  563. containers.unshift(rootGraph);
  564. containers[0].strict = (null != graphMatchRe.matched(1));
  565. containers[0].type = ('graph' == graphMatchRe.matched(2)) ? 'undirected' : 'directed';
  566. containers[0].attrs.set('xdotversion', '1.0');
  567. graphs.push(containers[0]);
  568. }
  569. } else {
  570. matches = subgraphMatchRe.match(line);
  571. if (matches) {
  572. containers.unshift( new VizGraph(subgraphMatchRe.matched(1), this, rootGraph, containers[0]) );
  573. containers[1].subgraphs.push(containers[0]);
  574. }
  575. }
  576. if (matches) {
  577. //Logger.log('begin container ' + containers[0].name);
  578. } else if ('}' == line) {
  579. //Logger.log('end container ' + containers[0].name);
  580. containers.shift();
  581. if (0 == containers.length) {
  582. break;
  583. }
  584. } else {
  585. matches = nodeMatchRe.match(line);
  586. if (matches) {
  587. entityName = nodeMatchRe.matched(2);
  588. attrs = nodeMatchRe.matched(5);
  589. drawAttrHash = containers[0].drawAttrs;
  590. isGraph = false;
  591. switch (entityName) {
  592. case 'graph':
  593. attrHash = containers[0].attrs;
  594. isGraph = true;
  595. case 'node':
  596. attrHash = containers[0].nodeAttrs;
  597. case 'edge':
  598. attrHash = containers[0].edgeAttrs;
  599. default:
  600. entity = new VizNode(entityName, this, rootGraph, containers[0]);
  601. attrHash = entity.attrs;
  602. drawAttrHash = entity.drawAttrs;
  603. containers[0].nodes.push(cast(entity,VizNode));
  604. }
  605. } else {
  606. matches = edgeMatchRe.match(line);
  607. if (matches) {
  608. entityName = edgeMatchRe.matched(1);
  609. attrs = edgeMatchRe.matched(8);
  610. entity = new VizEdge(entityName, this, rootGraph, containers[0], edgeMatchRe.matched(2), edgeMatchRe.matched(5));
  611. attrHash = entity.attrs;
  612. drawAttrHash = entity.drawAttrs;
  613. containers[0].edges.push(cast(entity,VizEdge));
  614. }
  615. }
  616. if (matches) {
  617. do {
  618. if (0 == attrs.length)
  619. break;
  620. matches = attrMatchRe.match(attrs);
  621. if (matches) {
  622. attrs = attrs.substr(attrMatchRe.matched(0).length);
  623. attrName = attrMatchRe.matched(1);
  624. attrValue = unescape(attrMatchRe.matched(2));
  625. if (~/^_.*draw_$/.match(attrName)) {
  626. drawAttrHash.set(attrName, attrValue);
  627. } else {
  628. attrHash.set(attrName, attrValue);
  629. }
  630. if (isGraph && 1 == containers.length) {
  631. switch (attrName) {
  632. case 'bb':
  633. var bb = attrValue.split(',');
  634. width = Std.parseFloat(bb[2]);
  635. height = Std.parseFloat(bb[3]);
  636. case 'bgcolor':
  637. bgcolor = rootGraph.parseColorFlash(attrValue);
  638. case 'dpi':
  639. dpi = Std.parseFloat( attrValue );
  640. case 'size':
  641. var sizeRe:EReg = ~/^(\d+|\d*(?:\.\d+)),\s*(\d+|\d*(?:\.\d+))(!?)$/;
  642. var size = sizeRe.match(attrValue);
  643. if (size) {
  644. maxWidth = 72 * Std.parseFloat(sizeRe.matched(1));
  645. maxHeight = 72 * Std.parseFloat(sizeRe.matched(2));
  646. bbEnlarge = ('!' == sizeRe.matched(3));
  647. } else {
  648. //Logger.log('can\'t parse size');
  649. }
  650. case 'xdotversion':
  651. if (0 > this.versionCompare(this.maxXdotVersion, attrHash.get('xdotversion'))) {
  652. //Logger.log('unsupported xdotversion ' + attrHash.get('xdotversion') + '; ' +
  653. // 'this script currently supports up to xdotversion ' + this.maxXdotVersion);
  654. }
  655. }
  656. }
  657. } else {
  658. //Logger.log('can\'t read attributes for entity ' + entityName + ' from ' + attrs);
  659. }
  660. } while (matches);
  661. }
  662. }
  663. }
  664. }
  665. draw();
  666. }
  667. function draw(?redrawCanvasOnly:Bool = false) {
  668. var ctxScale = this.scale * this.dpi / 72;
  669. var width = Math.round(ctxScale * this.width + 2 * this.padding);
  670. var height = Math.round(ctxScale * this.height + 2 * this.padding);
  671. if (!redrawCanvasOnly) {
  672. this.canvas.width = width;
  673. this.canvas.height = height;
  674. while (canvas.numChildren > 0)
  675. canvas.removeChildAt(0);
  676. this.canvas.graphics.clear();
  677. }
  678. canvas.x = canvas.y = this.padding;
  679. canvas.scaleX = canvas.scaleY = ctxScale;
  680. canvas.graphics.lineStyle(lineWidth, lineColor, lineAlpha, null, null, lineCap);
  681. graphs[0].draw(canvas.graphics, ctxScale, redrawCanvasOnly);
  682. var maxScale = 1.0;
  683. if (maxWidth != -1.0 && (canvas.width + 2 * padding) > maxWidth) {
  684. maxScale = maxWidth / (canvas.width + 2 * padding);
  685. }
  686. if (maxHeight != -1.0 && (canvas.height + 2 * padding) > maxHeight) {
  687. var tmp = maxHeight / (canvas.height + 2 * padding);
  688. if (tmp < maxScale)
  689. maxScale = tmp;
  690. }
  691. if (maxScale == 1.0) {
  692. var stageW = canvas.stage.stageWidth;
  693. var stageH = canvas.stage.stageHeight;
  694. if (stageW < canvas.width || stageH < canvas.height) {
  695. var scaleFactor = stageW / canvas.width;
  696. if ((stageH / canvas.height) < scaleFactor)
  697. scaleFactor = stageH / canvas.height;
  698. canvas.scaleX = scaleFactor;
  699. canvas.scaleY = scaleFactor;
  700. }
  701. } else {
  702. canvas.width *= ctxScale * maxScale;
  703. canvas.height *= ctxScale * maxScale;
  704. }
  705. }
  706. public function drawPath(ctx:Graphics, path:Path, filled:Bool, dashStyle:String) {
  707. if (filled) {
  708. path.makePath(ctx);
  709. ctx.endFill();
  710. }
  711. if (this.fillStyle != this.strokeStyle || !filled) {
  712. var oldLineWidth = -999.0;
  713. switch (dashStyle) {
  714. case 'dashed':
  715. path.makeDashedPath(ctx, this.dashLength);
  716. case 'dotted':
  717. oldLineWidth = this.lineWidth;
  718. this.lineWidth *= 2;
  719. ctx.lineStyle(lineWidth, lineColor, lineAlpha);
  720. path.makeDottedPath(ctx, this.dotSpacing);
  721. default: // 'solid', etc
  722. if (!filled) {
  723. path.makePath(ctx);
  724. }
  725. }
  726. if (oldLineWidth != -999.0) {
  727. this.lineWidth = oldLineWidth;
  728. ctx.lineStyle(lineWidth, lineColor, lineAlpha);
  729. }
  730. }
  731. }
  732. function unescape(str:String) :String {
  733. var r:EReg = ~/^"(.*)"$/;
  734. var matches = r.match(str);
  735. if (matches) {
  736. return ~/\\"/g.replace(r.matched(1), '"');
  737. } else {
  738. return str;
  739. }
  740. }
  741. public function parseHexColor(color:String) {
  742. var r:EReg = ~/^#([0-9a-f]{2})\s*([0-9a-f]{2})\s*([0-9a-f]{2})\s*([0-9a-f]{2})?$/i;
  743. var matches = r.match(color);
  744. var canvasColor:String = '0xff00ff'; // own default
  745. var textColor = '#' + r.matched(1) + r.matched(2) + r.matched(3);
  746. var opacity = 1.0;
  747. if (matches) {
  748. if (r.matched(4) != null) { // rgba
  749. opacity = Std.parseInt('0x'+r.matched(4)) / 255;
  750. canvasColor = 'rgba(' + Std.string(Std.parseInt('0x' + r.matched(1))) + ',' + Std.string(Std.parseInt('0x' + r.matched(2))) + ',' +
  751. Std.string(Std.parseInt('0x'+r.matched(3))) + ',' + Std.string(opacity) + ')';
  752. } else { // rgb
  753. canvasColor = textColor;
  754. }
  755. }
  756. return {canvasColor: canvasColor, textColor: textColor, opacity: opacity};
  757. }
  758. public function hsvToRgbColor(h:Int, s:Int, v:Int) {
  759. var i:Int;
  760. var f, p, q, t;
  761. var r:Float = 0xff, g:Float = 0xff, b:Float = 0xff;
  762. h *= 360;
  763. i = Math.floor(h / 60) % 6;
  764. f = h / 60 - i;
  765. p = v * (1 - s);
  766. q = v * (1 - f * s);
  767. t = v * (1 - (1 - f) * s);
  768. switch (i) {
  769. case 0: r = v; g = t; b = p;
  770. case 1: r = q; g = v; b = p;
  771. case 2: r = p; g = v; b = t;
  772. case 3: r = p; g = q; b = v;
  773. case 4: r = t; g = p; b = v;
  774. case 5: r = v; g = p; b = q;
  775. }
  776. return 'rgb(' + Math.round(255 * r) + ',' + Math.round(255 * g) + ',' + Math.round(255 * b) + ')';
  777. }
  778. function versionCompare(a:String, b:String):Int {
  779. var arr = a.split('.');
  780. var brr = b.split('.');
  781. var a1, b1;
  782. while (arr.length > 0 || brr.length > 0) {
  783. a1 = arr.length > 0 ? arr.shift() : '0';
  784. b1 = brr.length > 0 ? brr.shift() : '0';
  785. if (a1 < b1) return -1;
  786. if (a1 > b1) return 1;
  787. }
  788. return 0;
  789. }
  790. }
  791. class VizImage {
  792. var viz:Viz;
  793. var src:String;
  794. var finished:Bool;
  795. var loaded:Bool;
  796. var img:String;
  797. public function new(viz:Viz, src:String) {
  798. this.viz = viz;
  799. this.src = src;
  800. finished = true;
  801. loaded = false;
  802. }
  803. public function draw(ctx:Graphics, l:Float, t:Float, w:Float, h:Float) {
  804. if (this.finished) {
  805. if (this.loaded) {
  806. //drawImage(ctx, this.img, l, t, w, h);
  807. //Logger.log("draw: loaded");
  808. } else {
  809. this.drawBrokenImage(ctx, l, t, w, h);
  810. }
  811. }
  812. }
  813. public function drawBrokenImage(ctx:Graphics, l:Float, t:Float, w:Float, h:Float) {
  814. //Logger.log("drawBrokenImage");
  815. }
  816. }
  817. class Logger {
  818. #if flash
  819. static var t:flash.text.TextField;
  820. public static function log(msg:String) {
  821. if (t == null) {
  822. t = new flash.text.TextField();
  823. t.width = t.height = 400.;
  824. flash.Lib.current.addChild(t);
  825. }
  826. t.text += msg + "\n";
  827. }
  828. #else
  829. public static function log(msg:String) {
  830. trace(msg + "\n");
  831. }
  832. #end
  833. }