001/* 002 * Copyright 2006 - 2013 003 * Stefan Balev <stefan.balev@graphstream-project.org> 004 * Julien Baudry <julien.baudry@graphstream-project.org> 005 * Antoine Dutot <antoine.dutot@graphstream-project.org> 006 * Yoann Pigné <yoann.pigne@graphstream-project.org> 007 * Guilhelm Savin <guilhelm.savin@graphstream-project.org> 008 * 009 * This file is part of GraphStream <http://graphstream-project.org>. 010 * 011 * GraphStream is a library whose purpose is to handle static or dynamic 012 * graph, create them from scratch, file or any source and display them. 013 * 014 * This program is free software distributed under the terms of two licenses, the 015 * CeCILL-C license that fits European law, and the GNU Lesser General Public 016 * License. You can use, modify and/ or redistribute the software under the terms 017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following 018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by 019 * the Free Software Foundation, either version 3 of the License, or (at your 020 * option) any later version. 021 * 022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY 023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 024 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 025 * 026 * You should have received a copy of the GNU Lesser General Public License 027 * along with this program. If not, see <http://www.gnu.org/licenses/>. 028 * 029 * The fact that you are presently reading this means that you have had 030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms. 031 */ 032package org.graphstream.stream.file; 033 034import java.awt.Color; 035import java.io.IOException; 036import java.io.PrintWriter; 037import java.lang.reflect.Array; 038import java.util.HashMap; 039import java.util.HashSet; 040import java.util.LinkedList; 041import java.util.Locale; 042 043import org.graphstream.graph.Edge; 044import org.graphstream.graph.Element; 045import org.graphstream.graph.Node; 046import org.graphstream.stream.GraphReplay; 047import org.graphstream.ui.geom.Point3; 048import org.graphstream.ui.graphicGraph.GraphicEdge; 049import org.graphstream.ui.graphicGraph.GraphicGraph; 050import org.graphstream.ui.graphicGraph.GraphicNode; 051import org.graphstream.ui.graphicGraph.StyleGroup; 052import org.graphstream.ui.graphicGraph.StyleGroupSet; 053import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.FillMode; 054import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.SizeMode; 055import org.graphstream.ui.layout.springbox.implementations.SpringBox; 056 057/** 058 * An export of a graph to PGF/TikZ format. 059 * <a>http://sourceforge.net/projects/pgf/</a> 060 * 061 * This allows to include graph in a latex document. Only 062 * <code>writeAll(Graph,*)</code> is working, dynamics is not handle. If the 063 * exported graph is a GraphicGraph, then CSS style of the graph will be used. 064 * 065 * For a better rendering, it is strongly recommended to run previously a layout 066 * algorithm that will add coordinates on nodes. Else, random coordinates will 067 * be choosen for nodes. Layout can be run in this way : <code> 068 * Graph g; 069 * ... 070 * SpringBox sbox = new SpringBox(); 071 * 072 * g.addSink(sbox); 073 * sbox.addAttributeSink(g); 074 * 075 * do sbox.compute(); while (sbox.getStabilization() < 0.9); 076 * 077 * g.removeSink(sbox); 078 * sbox.remoteAttributeSink(g); 079 * </code> 080 * 081 * TikZ pictures are scalable so pixel units is not handle here. The picture is 082 * bounded in a box which width and height can be defined by adding attributes 083 * to the graph: 084 * <ul> 085 * <li>"ui.tikz.width"</li> 086 * <li>"ui.tikz.height"</li> 087 * </ul> 088 * The value of these attributes has to be considered as centimeters. 089 * 090 * Common supported style : 091 * <ul> 092 * <li>"fill-color", alpha is supported to</li> 093 * <li>"size" in "gu"</li> 094 * </ul> 095 * 096 * Node supported style : 097 * <ul> 098 * <li>"shape" with "box", "rounded-box", "circle", "triangle", "diamond"</li> 099 * <li>"stroke-mode" with "plain"</li> 100 * <li>"stroke-color", alpha is supported to</li> 101 * <li>"stroke-width" in "gu"</li> 102 * </ul> 103 * 104 * Edge supported style : 105 * <ul> 106 * </ul> 107 */ 108public class FileSinkTikZ extends FileSinkBase { 109 110 /** 111 * Node attribute storing coordinates. 112 */ 113 public static final String XYZ_ATTR = "xyz"; 114 115 /** 116 * Graph attribute storing width of the TikZ picture. 117 */ 118 public static final String WIDTH_ATTR = "ui.tikz.width"; 119 120 /** 121 * Graph attribute storing height of the TikZ picture. 122 */ 123 public static final String HEIGHT_ATTR = "ui.tikz.height"; 124 125 public static final double DEFAULT_WIDTH = 10; 126 127 public static final double DEFAULT_HEIGHT = 10; 128 129 /** 130 * Define the default minimum size of nodes when using a dynamic size. This 131 * size is in millimeter. 132 */ 133 public static final double DISPLAY_MIN_SIZE_IN_MM = 2; 134 135 /** 136 * Define the default maximum size of nodes when using a dynamic size. This 137 * size is in millimeter. 138 */ 139 public static final double DISPLAY_MAX_SIZE_IN_MM = 10; 140 141 protected PrintWriter out; 142 143 protected HashMap<String, String> colors = new HashMap<String, String>(); 144 protected HashMap<String, String> classes = new HashMap<String, String>(); 145 protected HashMap<String, String> classNames = new HashMap<String, String>(); 146 147 protected int classIndex = 0; 148 protected int colorIndex = 0; 149 150 protected double width = Double.NaN; 151 protected double height = Double.NaN; 152 153 protected boolean layout = false; 154 155 protected GraphicGraph buffer; 156 157 protected String css = null; 158 159 protected double minSize = 0; 160 161 protected double maxSize = 0; 162 163 protected double displayMinSize = DISPLAY_MIN_SIZE_IN_MM; 164 165 protected double displayMaxSize = DISPLAY_MAX_SIZE_IN_MM; 166 167 private double xmin, ymin, xmax, ymax; 168 169 private PointsWrapper points; 170 private Locale l = Locale.ROOT; 171 172 protected static String formatId(String id) { 173 return "node" + id.replaceAll("\\W", "_"); 174 } 175 176 public FileSinkTikZ() { 177 buffer = new GraphicGraph("tikz-buffer"); 178 } 179 180 public double getWidth() { 181 return width; 182 } 183 184 public void setWidth(double width) { 185 this.width = width; 186 } 187 188 public double getHeight() { 189 return height; 190 } 191 192 public void setHeight(double height) { 193 this.height = height; 194 } 195 196 public void setDisplaySize(double min, double max) { 197 this.displayMinSize = min; 198 this.displayMaxSize = max; 199 } 200 201 public void setCSS(String css) { 202 this.css = css; 203 } 204 205 public void setLayout(boolean layout) { 206 this.layout = layout; 207 } 208 209 protected double getNodeX(Node n) { 210 if (n.hasAttribute(XYZ_ATTR)) 211 return ((Number) (n.getArray(XYZ_ATTR)[0])).doubleValue(); 212 213 if (n.hasAttribute("x")) 214 return n.getNumber("x"); 215 216 return Double.NaN; 217 } 218 219 protected double getNodeY(Node n) { 220 if (n.hasAttribute(XYZ_ATTR)) 221 return ((Number) (n.getArray(XYZ_ATTR)[1])).doubleValue(); 222 223 if (n.hasAttribute("y")) 224 return n.getNumber("y"); 225 226 return Double.NaN; 227 } 228 229 protected String getNodeStyle(Node n) { 230 String style = "tikzgsnode"; 231 232 if (n instanceof GraphicNode) { 233 GraphicNode gn = (GraphicNode) n; 234 235 style = classNames.get(gn.style.getId()); 236 237 if (gn.style.getFillMode() == FillMode.DYN_PLAIN) { 238 double uicolor = gn.getNumber("ui.color"); 239 240 if (Double.isNaN(uicolor)) 241 uicolor = 0; 242 243 int c = gn.style.getFillColorCount(); 244 int s = 1; 245 double d = 1.0 / (c - 1); 246 247 while (s * d < uicolor && s < c) 248 s++; 249 250 uicolor -= (s - 1) * d; 251 uicolor *= c; 252 253 style += String.format(Locale.ROOT, ", fill=%s!%d!%s", 254 checkColor(gn.style.getFillColor(0)), 255 (int) (uicolor * 100), 256 checkColor(gn.style.getFillColor(1))); 257 } 258 259 if (gn.style.getSizeMode() == SizeMode.DYN_SIZE) { 260 double uisize = gn.getNumber("ui.size"); 261 262 if (Double.isNaN(uisize)) 263 uisize = minSize; 264 265 uisize = (uisize - minSize) / (maxSize - minSize); 266 uisize = uisize * (displayMaxSize - displayMinSize) 267 + displayMinSize; 268 269 style += String.format(Locale.ROOT, ", minimum size=%fmm", 270 uisize); 271 } 272 } 273 274 return style; 275 } 276 277 protected String getEdgeStyle(Edge e) { 278 String style = "tikzgsnode"; 279 280 if (e instanceof GraphicEdge) { 281 GraphicEdge ge = (GraphicEdge) e; 282 283 style = classNames.get(ge.style.getId()); 284 285 if (ge.style.getFillMode() == FillMode.DYN_PLAIN) { 286 double uicolor = ge.getNumber("ui.color"); 287 288 if (Double.isNaN(uicolor)) 289 uicolor = 0; 290 291 int c = ge.style.getFillColorCount(); 292 int s = 1; 293 double d = 1.0 / (c - 1); 294 295 while (s * d < uicolor && s < c) 296 s++; 297 298 uicolor -= (s - 1) * d; 299 uicolor *= c; 300 301 style += String.format(Locale.ROOT, ", draw=%s!%d!%s", 302 checkColor(ge.style.getFillColor(s - 1)), 303 (int) (uicolor * 100), 304 checkColor(ge.style.getFillColor(s))); 305 } 306 307 if (ge.style.getSizeMode() == SizeMode.DYN_SIZE) { 308 double uisize = ge.getNumber("ui.size"); 309 310 if (Double.isNaN(uisize) || uisize < 0.01) 311 uisize = 1; 312 313 style += String 314 .format(Locale.ROOT, ", line width=%fpt", uisize); 315 } 316 } 317 318 return style; 319 } 320 321 protected String checkColor(Color c) { 322 String rgb = String.format(Locale.ROOT, "%.3f,%.3f,%.3f", 323 c.getRed() / 255.0f, c.getGreen() / 255.0f, 324 c.getBlue() / 255.0f); 325 326 if (colors.containsKey(rgb)) 327 return colors.get(rgb); 328 329 String key = String.format("tikzC%02d", colorIndex++); 330 colors.put(rgb, key); 331 332 return key; 333 } 334 335 /** 336 * Convert a StyleGroup to tikz style. 337 * 338 * @param group 339 * the style group to convert 340 * @return string representation of the style group usable in TikZ. 341 */ 342 protected String getTikzStyle(StyleGroup group) { 343 StringBuilder buffer = new StringBuilder(); 344 LinkedList<String> style = new LinkedList<String>(); 345 346 for (int i = 0; i < group.getFillColorCount(); i++) 347 checkColor(group.getFillColor(i)); 348 349 switch (group.getType()) { 350 case NODE: { 351 if (group.getFillMode() != FillMode.DYN_PLAIN) { 352 String fill = checkColor(group.getFillColor(0)); 353 style.add("fill=" + fill); 354 } 355 356 if (group.getFillColor(0).getAlpha() < 255) 357 style.add(String.format(Locale.ROOT, "fill opacity=%.2f", group 358 .getFillColor(0).getAlpha() / 255.0f)); 359 360 switch (group.getStrokeMode()) { 361 case DOTS: 362 case DASHES: 363 case PLAIN: 364 String stroke = checkColor(group.getStrokeColor(0)); 365 style.add("draw=" + stroke); 366 style.add("line width=" 367 + String.format(Locale.ROOT, "%.1fpt", 368 group.getStrokeWidth().value)); 369 370 if (group.getStrokeColor(0).getAlpha() < 255) 371 style.add(String.format(Locale.ROOT, "draw opacity=%.2f", 372 group.getStrokeColor(0).getAlpha() / 255.0f)); 373 374 break; 375 default: 376 System.err.printf("unhandled stroke mode : %s%n", 377 group.getStrokeMode()); 378 } 379 380 switch (group.getShape()) { 381 case CIRCLE: 382 style.add("circle"); 383 break; 384 case ROUNDED_BOX: 385 style.add("rounded corners=2pt"); 386 case BOX: 387 style.add("rectangle"); 388 break; 389 case TRIANGLE: 390 style.add("isosceles triangle"); 391 break; 392 case DIAMOND: 393 style.add("diamond"); 394 break; 395 default: 396 System.err.printf("unhandled shape : %s%n", group.getShape()); 397 } 398 399 String text = checkColor(group.getTextColor(0)); 400 style.add("text=" + text); 401 402 switch (group.getSize().units) { 403 case GU: 404 style.add("minimum size=" 405 + String.format(Locale.ROOT, "%.1fcm", 406 group.getSize().values.get(0))); 407 break; 408 case PX: 409 style.add("minimum size=" 410 + String.format(Locale.ROOT, "%.1fpt", 411 group.getSize().values.get(0))); 412 break; 413 default: 414 System.err 415 .printf("%% [warning] units %s are not compatible with TikZ.%n", 416 group.getSize().units); 417 } 418 419 style.add("inner sep=0pt"); 420 } 421 break; 422 case EDGE: { 423 if (group.getFillMode() != FillMode.DYN_PLAIN) { 424 String fill = checkColor(group.getFillColor(0)); 425 style.add("draw=" + fill); 426 } 427 428 if (group.getFillColor(0).getAlpha() < 255) 429 style.add(String.format(Locale.ROOT, "draw opacity=%.2f", group 430 .getFillColor(0).getAlpha() / 255.0f)); 431 432 switch (group.getSize().units) { 433 case PX: 434 case GU: 435 style.add("line width=" 436 + String.format(Locale.ROOT, "%.1fpt", 437 group.getSize().values.get(0))); 438 break; 439 default: 440 System.err 441 .printf("%% [warning] units %s are not compatible with TikZ.%n", 442 group.getSize().units); 443 } 444 } 445 break; 446 default: 447 System.err.printf("unhandled group type : %s%n", group.getType()); 448 } 449 450 for (int i = 0; i < style.size(); i++) { 451 if (i > 0) 452 buffer.append(","); 453 454 buffer.append(style.get(i)); 455 } 456 457 return buffer.toString(); 458 } 459 460 /* 461 * (non-Javadoc) 462 * 463 * @see org.graphstream.stream.file.FileSinkBase#outputHeader() 464 */ 465 protected void outputHeader() throws IOException { 466 out = (PrintWriter) output; 467 468 colors.clear(); 469 classes.clear(); 470 classNames.clear(); 471 472 buffer.clear(); 473 } 474 475 /* 476 * (non-Javadoc) 477 * 478 * @see org.graphstream.stream.file.FileSinkBase#outputEndOfFile() 479 */ 480 protected void outputEndOfFile() throws IOException { 481 if (Double.isNaN(width)) { 482 if (buffer.hasNumber(WIDTH_ATTR)) 483 width = buffer.getNumber(WIDTH_ATTR); 484 else 485 width = DEFAULT_WIDTH; 486 } 487 488 if (Double.isNaN(height)) { 489 if (buffer.hasNumber(HEIGHT_ATTR)) 490 height = buffer.getNumber(HEIGHT_ATTR); 491 else 492 height = DEFAULT_WIDTH; 493 } 494 495 checkLayout(); 496 497 if (css != null) 498 buffer.addAttribute("ui.stylesheet", css); 499 500 points = new PointsWrapper(); 501 502 // 503 // Begin tikzpicture 504 // 505 out.printf("%%%n%% Do not forget \\usepackage{tikz} in header.%n%%%n"); 506 out.printf("\\begin{tikzpicture}"); 507 508 checkAndOutputStyle(); 509 checkXYandSize(); 510 511 for (Node n : buffer.getEachNode()) { 512 double x, y; 513 514 x = getNodeX(n); 515 y = getNodeY(n); 516 517 if (Double.isNaN(x) || Double.isNaN(y)) { 518 x = Math.random() * width; 519 y = Math.random() * height; 520 } else { 521 x = width * (x - xmin) / (xmax - xmin); 522 y = height * (y - ymin) / (ymax - ymin); 523 } 524 525 out.printf(l, "\t\\node[inner sep=0pt] (%s) at (%f,%f) {};%n", 526 formatId(n.getId()), x, y); 527 } 528 529 StyleGroupSet sgs = buffer.getStyleGroups(); 530 531 for (HashSet<StyleGroup> groups : sgs.zIndex()) { 532 for (StyleGroup group : groups) { 533 switch (group.getType()) { 534 case NODE: 535 for (Element e : group.elements()) 536 outputNode((Node) e); 537 break; 538 case EDGE: 539 for (Element e : group.elements()) 540 outputEdge((Edge) e); 541 break; 542 default: 543 } 544 } 545 } 546 547 // 548 // End of tikzpicture. 549 // 550 out.printf("\\end{tikzpicture}%n"); 551 } 552 553 private void checkLayout() { 554 if (!layout) 555 return; 556 557 SpringBox sbox = new SpringBox(); 558 559 GraphReplay replay = new GraphReplay("replay"); 560 replay.addSink(sbox); 561 sbox.addAttributeSink(buffer); 562 563 replay.replay(buffer); 564 565 do 566 sbox.compute(); 567 while (sbox.getStabilization() < 0.9); 568 569 buffer.removeSink(sbox); 570 sbox.removeAttributeSink(buffer); 571 } 572 573 private void checkXYandSize() { 574 xmin = ymin = Double.MAX_VALUE; 575 xmax = ymax = Double.MIN_VALUE; 576 577 for (Node n : buffer.getEachNode()) { 578 double x, y; 579 580 x = getNodeX(n); 581 y = getNodeY(n); 582 583 if (!Double.isNaN(x) && !Double.isNaN(y)) { 584 xmin = Math.min(xmin, x); 585 xmax = Math.max(xmax, x); 586 ymin = Math.min(ymin, y); 587 ymax = Math.max(ymax, y); 588 } else { 589 System.err.printf("%% [warning] missing node (x,y).%n"); 590 } 591 592 if (n.hasNumber("ui.size")) { 593 minSize = Math.min(minSize, n.getNumber("ui.size")); 594 maxSize = Math.max(maxSize, n.getNumber("ui.size")); 595 } 596 } 597 598 if (minSize == maxSize) 599 maxSize += 1; 600 601 for (Edge e : buffer.getEachEdge()) { 602 points.setElement(e); 603 604 if (points.check()) { 605 for (int i = 0; i < points.getPointsCount(); i++) { 606 double x = points.getX(i); 607 double y = points.getY(i); 608 609 xmin = Math.min(xmin, x); 610 xmax = Math.max(xmax, x); 611 ymin = Math.min(ymin, y); 612 ymax = Math.max(ymax, y); 613 } 614 } 615 } 616 } 617 618 private void checkAndOutputStyle() { 619 String nodeStyle = "circle,draw=black,fill=black"; 620 String edgeStyle = "draw=black"; 621 StyleGroupSet sgs = buffer.getStyleGroups(); 622 623 for (StyleGroup sg : sgs.groups()) { 624 String key = String.format("class%02d", classIndex++); 625 classNames.put(sg.getId(), key); 626 classes.put(key, getTikzStyle(sg)); 627 } 628 629 out.printf("[%n"); 630 631 for (String key : classes.keySet()) 632 out.printf(l, "\t%s/.style={%s},%n", key, classes.get(key)); 633 634 out.printf(l, "\ttikzgsnode/.style={%s},%n", nodeStyle); 635 out.printf(l, "\ttikzgsedge/.style={%s}%n", edgeStyle); 636 637 out.printf("]%n"); 638 639 for (String rgb : colors.keySet()) 640 out.printf(l, "\t\\definecolor{%s}{rgb}{%s}%n", colors.get(rgb), 641 rgb); 642 } 643 644 private void outputNode(Node n) { 645 String label; 646 String style = getNodeStyle(n); 647 648 label = n.hasAttribute("label") ? (String) n.getLabel("label") : ""; 649 650 out.printf(l, "\t\\node[%s] at (%s) {%s};%n", style, 651 formatId(n.getId()), label); 652 } 653 654 private void outputEdge(Edge e) { 655 String style = getEdgeStyle(e); 656 String uiPoints = ""; 657 points.setElement(e); 658 659 if (points.check()) { 660 for (int i = 0; i < points.getPointsCount(); i++) { 661 double x, y; 662 663 x = points.getX(i); 664 y = points.getY(i); 665 x = width * (x - xmin) / (xmax - xmin); 666 y = height * (y - ymin) / (ymax - ymin); 667 668 uiPoints = String 669 .format(l, "%s-- (%.3f,%.3f) ", uiPoints, x, y); 670 } 671 } 672 673 out.printf(l, "\t\\draw[%s] (%s) %s%s (%s);%n", style, formatId(e 674 .getSourceNode().getId()), uiPoints, e.isDirected() ? "->" 675 : "--", formatId(e.getTargetNode().getId())); 676 } 677 678 /* 679 * (non-Javadoc) 680 * 681 * @see 682 * org.graphstream.stream.AttributeSink#graphAttributeAdded(java.lang.String 683 * , long, java.lang.String, java.lang.Object) 684 */ 685 public void graphAttributeAdded(String sourceId, long timeId, 686 String attribute, Object value) { 687 buffer.graphAttributeAdded(sourceId, timeId, attribute, value); 688 } 689 690 /* 691 * (non-Javadoc) 692 * 693 * @see 694 * org.graphstream.stream.AttributeSink#graphAttributeChanged(java.lang. 695 * String, long, java.lang.String, java.lang.Object, java.lang.Object) 696 */ 697 public void graphAttributeChanged(String sourceId, long timeId, 698 String attribute, Object oldValue, Object newValue) { 699 buffer.graphAttributeChanged(sourceId, timeId, attribute, oldValue, 700 newValue); 701 } 702 703 /* 704 * (non-Javadoc) 705 * 706 * @see 707 * org.graphstream.stream.AttributeSink#graphAttributeRemoved(java.lang. 708 * String, long, java.lang.String) 709 */ 710 public void graphAttributeRemoved(String sourceId, long timeId, 711 String attribute) { 712 buffer.graphAttributeRemoved(sourceId, timeId, attribute); 713 } 714 715 /* 716 * (non-Javadoc) 717 * 718 * @see 719 * org.graphstream.stream.AttributeSink#nodeAttributeAdded(java.lang.String, 720 * long, java.lang.String, java.lang.String, java.lang.Object) 721 */ 722 public void nodeAttributeAdded(String sourceId, long timeId, String nodeId, 723 String attribute, Object value) { 724 buffer.nodeAttributeAdded(sourceId, timeId, nodeId, attribute, value); 725 } 726 727 /* 728 * (non-Javadoc) 729 * 730 * @see 731 * org.graphstream.stream.AttributeSink#nodeAttributeChanged(java.lang.String 732 * , long, java.lang.String, java.lang.String, java.lang.Object, 733 * java.lang.Object) 734 */ 735 public void nodeAttributeChanged(String sourceId, long timeId, 736 String nodeId, String attribute, Object oldValue, Object newValue) { 737 buffer.nodeAttributeChanged(sourceId, timeId, nodeId, attribute, 738 oldValue, newValue); 739 } 740 741 /* 742 * (non-Javadoc) 743 * 744 * @see 745 * org.graphstream.stream.AttributeSink#nodeAttributeRemoved(java.lang.String 746 * , long, java.lang.String, java.lang.String) 747 */ 748 public void nodeAttributeRemoved(String sourceId, long timeId, 749 String nodeId, String attribute) { 750 buffer.nodeAttributeRemoved(sourceId, timeId, nodeId, attribute); 751 } 752 753 /* 754 * (non-Javadoc) 755 * 756 * @see 757 * org.graphstream.stream.AttributeSink#edgeAttributeAdded(java.lang.String, 758 * long, java.lang.String, java.lang.String, java.lang.Object) 759 */ 760 public void edgeAttributeAdded(String sourceId, long timeId, String edgeId, 761 String attribute, Object value) { 762 buffer.edgeAttributeAdded(sourceId, timeId, edgeId, attribute, value); 763 } 764 765 /* 766 * (non-Javadoc) 767 * 768 * @see 769 * org.graphstream.stream.AttributeSink#edgeAttributeChanged(java.lang.String 770 * , long, java.lang.String, java.lang.String, java.lang.Object, 771 * java.lang.Object) 772 */ 773 public void edgeAttributeChanged(String sourceId, long timeId, 774 String edgeId, String attribute, Object oldValue, Object newValue) { 775 buffer.edgeAttributeChanged(sourceId, timeId, edgeId, attribute, 776 oldValue, newValue); 777 } 778 779 /* 780 * (non-Javadoc) 781 * 782 * @see 783 * org.graphstream.stream.AttributeSink#edgeAttributeRemoved(java.lang.String 784 * , long, java.lang.String, java.lang.String) 785 */ 786 public void edgeAttributeRemoved(String sourceId, long timeId, 787 String edgeId, String attribute) { 788 buffer.edgeAttributeRemoved(sourceId, timeId, edgeId, attribute); 789 } 790 791 /* 792 * (non-Javadoc) 793 * 794 * @see org.graphstream.stream.ElementSink#nodeAdded(java.lang.String, long, 795 * java.lang.String) 796 */ 797 public void nodeAdded(String sourceId, long timeId, String nodeId) { 798 buffer.nodeAdded(sourceId, timeId, nodeId); 799 } 800 801 /* 802 * (non-Javadoc) 803 * 804 * @see org.graphstream.stream.ElementSink#nodeRemoved(java.lang.String, 805 * long, java.lang.String) 806 */ 807 public void nodeRemoved(String sourceId, long timeId, String nodeId) { 808 buffer.nodeRemoved(sourceId, timeId, nodeId); 809 } 810 811 /* 812 * (non-Javadoc) 813 * 814 * @see org.graphstream.stream.ElementSink#edgeAdded(java.lang.String, long, 815 * java.lang.String, java.lang.String, java.lang.String, boolean) 816 */ 817 public void edgeAdded(String sourceId, long timeId, String edgeId, 818 String fromNodeId, String toNodeId, boolean directed) { 819 buffer.edgeAdded(sourceId, timeId, edgeId, fromNodeId, toNodeId, 820 directed); 821 } 822 823 /* 824 * (non-Javadoc) 825 * 826 * @see org.graphstream.stream.ElementSink#edgeRemoved(java.lang.String, 827 * long, java.lang.String) 828 */ 829 public void edgeRemoved(String sourceId, long timeId, String edgeId) { 830 buffer.edgeRemoved(sourceId, timeId, edgeId); 831 } 832 833 /* 834 * (non-Javadoc) 835 * 836 * @see org.graphstream.stream.ElementSink#graphCleared(java.lang.String, 837 * long) 838 */ 839 public void graphCleared(String sourceId, long timeId) { 840 buffer.graphCleared(sourceId, timeId); 841 } 842 843 /* 844 * (non-Javadoc) 845 * 846 * @see org.graphstream.stream.ElementSink#stepBegins(java.lang.String, 847 * long, double) 848 */ 849 public void stepBegins(String sourceId, long timeId, double step) { 850 buffer.stepBegins(sourceId, timeId, step); 851 } 852 853 protected class PointsWrapper { 854 Object[] points; 855 856 PointsWrapper() { 857 } 858 859 public void setElement(Element e) { 860 if (e.hasArray("ui.points")) 861 points = e.getArray("ui.points"); 862 else 863 points = null; 864 } 865 866 public boolean check() { 867 if (points == null) 868 return false; 869 870 for (int i = 0; i < points.length; i++) { 871 if (!(points[i] instanceof Point3) 872 && !points[i].getClass().isArray()) 873 return false; 874 } 875 876 return true; 877 } 878 879 public int getPointsCount() { 880 return points == null ? 0 : points.length; 881 } 882 883 public double getX(int i) { 884 if (points == null || i >= points.length) 885 return Double.NaN; 886 887 Object p = points[i]; 888 889 if (p instanceof Point3) 890 return ((Point3) p).x; 891 else { 892 Object x = Array.get(p, 0); 893 894 if (x instanceof Number) 895 return ((Number) x).doubleValue(); 896 else 897 return Array.getDouble(p, 0); 898 } 899 } 900 901 public double getY(int i) { 902 if (i >= points.length) 903 return Double.NaN; 904 905 Object p = points[i]; 906 907 if (p instanceof Point3) 908 return ((Point3) p).y; 909 else { 910 Object y = Array.get(p, 0); 911 912 if (y instanceof Number) 913 return ((Number) y).doubleValue(); 914 else 915 return Array.getDouble(p, 1); 916 } 917 } 918 } 919}