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}