001    // GraphLab Project: http://graphlab.sharif.edu
002    // Copyright (C) 2008 Mathematical Science Department of Sharif University of Technology
003    // Distributed under the terms of the GNU General Public License (GPL): http://www.gnu.org/licenses/
004    package graphlab.graph.graph;
005    
006    import graphlab.graph.event.*;
007    import graphlab.graph.old.ArrowHandler;
008    import graphlab.graph.old.GShape;
009    import graphlab.graph.old.GStroke;
010    import graphlab.platform.core.BlackBoard;
011    import graphlab.platform.preferences.lastsettings.StorableOnExit;
012    import graphlab.platform.preferences.lastsettings.UserModifiableProperty;
013    
014    import java.awt.*;
015    import java.awt.geom.QuadCurve2D;
016    import java.awt.geom.Rectangle2D;
017    import java.util.Iterator;
018    
019    
020    public class FastRenderer extends AbstractGraphRenderer implements VertexModelListener, EdgeModelListener, GraphControlListener, StorableOnExit {
021        {
022            SETTINGS.registerSetting(this, "Graph Drawings");
023        }
024    
025        long lastpaintTime;
026        @UserModifiableProperty(displayName = "Default Vertex Radius", obeysAncestorCategory = false, category = "Rendering Options")
027        public static Integer defaultVertexRadius = 10;
028        public int vertexRadius = defaultVertexRadius;
029        private BlackBoard blackboard;
030    
031        private static GStroke markedStroke = GStroke.strong;
032        @UserModifiableProperty(displayName = "Default Edge Stroke")
033        public static GStroke defaultStroke = GStroke.solid;
034        @UserModifiableProperty(displayName = "Default Vertex Shape")
035        public static GShape defaultVertexShape = GShape.OVAL;
036        @UserModifiableProperty(displayName = "Default Vertex Color")
037        public static Color defaultVertexColor = new Color(116, 196, 255);
038        @UserModifiableProperty(displayName = "Default Vertex Stroke")
039        public static GStroke defaultBorderStroke = GStroke.dashed;
040        @UserModifiableProperty(displayName = "Default Size of Vertices")
041        public static Dimension defaultShapeDimension = new Dimension(20, 20);
042        @UserModifiableProperty(displayName = "Default Edge Color")
043        public static Color defaultEdgeColor = new Color(249, 117, 46);
044    
045        boolean drawVertexLabels;
046        boolean drawEdgeLabels;
047        boolean isGraphDirected;
048        boolean isEdgesCurved;
049        boolean isDirected;
050    
051        /**
052         * a cached version of  GraphModel's zoomFactor
053         */
054        double zoomFactor;
055    
056        GraphControl control;
057        private Rectangle2D.Double bounds = new Rectangle2D.Double(0, 0, 0, 0);
058    
059        public void setGraph(GraphModel g) {
060            super.setGraph(g);
061            for (VertexModel v : g) {
062                v.setVertexListener(this);
063            }
064            for (Iterator<EdgeModel> ie = g.edgeIterator(); ie.hasNext();) {
065                EdgeModel e = ie.next();
066                e.setEdgeModelListener(this);
067            }
068        }
069    
070        public FastRenderer(GraphModel g, BlackBoard blackboard) {
071            super();
072            this.blackboard = blackboard;
073            lastpaintTime = System.currentTimeMillis();
074            setGraph(g);
075            control = new GraphControl(g, this, blackboard);
076            control.setListener(this);
077            setFocusable(true);
078            setBackground(Color.white);
079            setLayout(null);
080            setBorder(null);
081            updateGraphBounds();
082        }
083    
084        boolean qpbc = true;
085        public boolean forceQuickPaint = false;
086    
087        public void render(Graphics2D gg, Boolean drawExtras) {
088    
089            this.zoomFactor = getGraph().getZoomFactor();
090    
091            boolean quickPaint = forceQuickPaint || ((getGraph().getVerticesCount() + getGraph().getEdgesCount()) >= 500);
092            /*this is for , if we want to the graph has a transparency over it's background image (if it has any one).
093            // Get and install an AlphaComposite to do transparent drawing
094            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
095    
096            g.fillRect(100, 100, 100, 100);               // Start drawing with it
097    
098            */
099    //        if (qpbc != quickPaint) {
100    //            qpbc = quickPaint;
101            if (quickPaint) {
102                gg.setRenderingHint(RenderingHints.KEY_RENDERING,
103                        RenderingHints.VALUE_RENDER_SPEED);
104                gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
105                        RenderingHints.VALUE_ANTIALIAS_OFF);
106                gg.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
107                        RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
108                gg.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
109                        RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
110                gg.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
111                        RenderingHints.VALUE_COLOR_RENDER_SPEED);
112                gg.setRenderingHint(RenderingHints.KEY_DITHERING,
113                        RenderingHints.VALUE_DITHER_DISABLE);
114            } else {
115                gg.setRenderingHint(RenderingHints.KEY_RENDERING,
116                        RenderingHints.VALUE_RENDER_QUALITY);
117                gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
118                        RenderingHints.VALUE_ANTIALIAS_ON);
119                gg.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
120                        RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
121                gg.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
122                        RenderingHints.VALUE_FRACTIONALMETRICS_ON);
123                gg.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
124                        RenderingHints.VALUE_COLOR_RENDER_QUALITY);
125                gg.setRenderingHint(RenderingHints.KEY_DITHERING,
126                        RenderingHints.VALUE_DITHER_ENABLE);
127            }
128    //        }
129    
130            paintGraph(gg, drawExtras);
131        }
132    
133        public void paintGraph(Graphics gg, Boolean drawExtras) {
134            if (ignoreRapaints)
135                return;
136            if (updateBounds) {
137                doUpdateGraphBounds();
138            }
139            if (isUpdated) {
140                isUpdated = false;
141                return;
142            }
143            //System.out.println("repaint");
144    
145            GraphModel graph = getGraph();
146            gg.setFont(graph.getFont());
147            boolean quickPaint = forceQuickPaint || (graph.getVerticesCount() + graph.getEdgesCount() >= 1000);
148            drawVertexLabels = graph.isDrawVertexLabels();
149            drawEdgeLabels = graph.isDrawEdgeLabels();
150            isGraphDirected = graph.isDirected();
151            isEdgesCurved = graph.isEdgesCurved();
152            isDirected = graph.isDirected();
153    
154            if (minx < 0) {
155                control.minx = minx;
156            } else {
157                control.minx = 0;
158            }
159            if (miny < 0) {
160                control.miny = miny;
161            } else
162                control.miny = 0;
163    
164            if (quickPaint) {
165                fastpaintGraph(gg, drawExtras);
166            } else {
167                nicepaintGraph(gg, drawExtras);
168            }
169        }
170    
171        public void nicepaintGraph(Graphics gg, Boolean drawExtras) {
172            lastpaintTime = System.currentTimeMillis();
173            Iterator<EdgeModel> ie = getGraph().lightEdgeIterator();
174            try {
175                while (ie.hasNext()) {
176                    EdgeModel e = ie.next();
177                    paint((Graphics2D) gg, e, getGraph(), drawExtras);
178                }
179            } catch (Exception e) {
180                System.err.println("(FastPaint: Paint Error:");
181                e.printStackTrace();
182    //            repaint();
183            }
184            gg.setColor(Color.darkGray);
185    
186            try {
187                for (VertexModel v : getGraph()) {
188                    GraphPoint l = v.getLocation();
189                    gg.setColor(GraphModel.getColor(v.getColor()));
190                    String s = v.getLabel();
191                    int dl = s.length() * 4;
192                    paint((Graphics2D) gg, v, zm(l.x) - v.getCenter().x, zm(l.y) - v.getCenter().y, zm(l.x) - dl, zm(l.y) + vertexRadius / 2, drawExtras);
193                }
194            } catch (Exception e) {
195                repaint();
196            }
197        }
198    
199        public void fastpaintGraph(Graphics g, Boolean drawExtras) {
200            Graphics2D gg = (Graphics2D) g;
201            lastpaintTime = System.currentTimeMillis();
202            Iterator<EdgeModel> ie = getGraph().lightEdgeIterator();
203            try {
204                while (ie.hasNext()) {
205                    EdgeModel e = ie.next();
206                    GraphPoint l, r;
207                    l = e.source.getLocation();
208                    r = e.target.getLocation();
209                    int ci = e.getColor();
210                    Color c;
211                    if (ci == 0)
212                        c = defaultEdgeColor;
213                    else
214                        c = GraphModel.getColor(ci);
215    
216                    if (e.getMark()) {
217                        c = Color.darkGray;
218                    }
219                    if (e.isSelected())
220                        c = c.darker();
221    
222                    gg.setColor(c);
223                    gg.drawLine((int) (l.x * zoomFactor), (int) (l.y * zoomFactor), (int) (r.x * zoomFactor), (int) (r.y * zoomFactor));
224                    if (isGraphDirected) {
225                        double angle = e.getAngle();
226    
227                        double t = Math.atan2(vertexRadius * Math.sin(angle), vertexRadius * Math.cos(angle));
228    
229                        GraphPoint loc = e.target.getLocation();
230                        int x2 = (int) (loc.x + (vertexRadius / 2) * Math.cos(t));
231                        int y2 = (int) (loc.y + (vertexRadius / 2) * Math.sin(t));
232    
233                        gg.translate(x2 * zoomFactor, y2 * zoomFactor);
234                        gg.rotate(angle + Math.PI);
235    
236                        gg.drawPolyline(new int[]{-5, 0, -5}, new int[]{-4, 0, 4}, 3);
237    //                    ArrowHandler.defaultArrow.paintArrow(g, 15, 15);
238                        gg.rotate(-angle - Math.PI);
239                        gg.translate(-x2 * zoomFactor, -y2 * zoomFactor);
240                    }
241                    if (drawEdgeLabels) {
242                        gg.setColor(c.darker().darker());
243                        String s = e.getLabel();
244                        int dl = (s.length() / 2) * 5;
245                        gg.drawString(s, (int) ((((l.x + r.x) / 2) * zoomFactor) - dl), (int) (((l.y + r.y) / 2) * zoomFactor));
246                    }
247                }
248            } catch (Exception e) {
249                repaint();
250            }
251            gg.setColor(Color.darkGray);
252    
253            try {
254                for (VertexModel v : getGraph()) {
255                    GraphPoint l = v.getLocation();
256                    int ci = v.getColor();
257                    Color c;
258                    if (ci == 0)
259                        c = defaultVertexColor;
260                    else
261                        c = GraphModel.getColor(ci);
262    
263                    if (v.isSelected())
264                        c = c.darker();
265                    if (v.getMark())
266                        c = Color.gray;
267    
268                    gg.setColor(c);
269                    gg.fillOval((int) (l.x * zoomFactor) - vertexRadius / 2, (int) (l.y * zoomFactor) - vertexRadius / 2, vertexRadius, vertexRadius);
270    
271                    if (drawVertexLabels) {
272                        String s = v.getLabel();
273                        int dl = (s.length() + 1 / 2) * 4;
274                        gg.setColor(c.darker().darker());
275                        GraphPoint ll = v.getLabelLocation();
276                        gg.drawString(s, (int) ((l.x - dl + ll.x) * zoomFactor), (int) ((l.y + vertexRadius / 2 + ll.y) * zoomFactor));
277                    }
278                    gg.setColor(Color.DARK_GRAY);
279                }
280            } catch (Exception e) {
281                repaint();
282            }
283        }
284    
285    
286        public void paint(Graphics2D g, EdgeModel model, GraphModel graph, Boolean drawExtras) {
287            if (!model.source.equals(model.target)) {
288                Color color;
289                if (model.getColor() == 0)
290                    color = defaultEdgeColor;
291                else
292                    color = GraphModel.getColor(model.getColor());
293                BasicStroke stroke = model.getStroke().stroke;
294                GraphPoint p1 = model.source.getLocation();
295                GraphPoint p2 = model.target.getLocation();
296    
297                if (model.isSelected()) {
298                    //            color = selectedColor;
299                    color = color.darker().darker();
300                }
301                if (model.getMark()) {
302                    //todo: omid: mark is boolean so we should have isMarked() in java definition.
303    //                color = markedColor;
304                    stroke = markedStroke.stroke;
305                }
306                Color c1, c2;
307                c1 = color;
308                c2 = color.darker().darker();
309    
310                double edgeLeft = Math.min(p1.x, p2.x);
311                double edgeTop = Math.min(p1.y, p2.y);
312                int d = GraphControl.EDGE_CURVE_CPNTROL_BOX_DIAMETER;
313                GraphPoint ctrlPnt = model.getCurveControlPoint();
314                double edgecenterx = (p1.x + p2.x) / 2;
315                int ctrlPntViewX = zm(edgecenterx + ctrlPnt.x);
316                double edgecentery = (p1.y + p2.y) / 2;
317                int ctrlPntViewY = zm(edgecentery + ctrlPnt.y);
318                int x1z = zm(p1.x);
319                int y1z = zm(p1.y);
320                int x2z = zm(p2.x);
321                int y2z = zm(p2.y);
322                if (isEdgesCurved && drawExtras) {
323                    //draw control boxes
324                    g.setColor(c1.darker());
325                    g.fillOval(ctrlPntViewX - d / 2, ctrlPntViewY - d / 2, d, d);
326                    //draw the edge line
327                    g.setColor(c1);
328                    g.setStroke(stroke);
329                    if (ctrlPnt.x == 0 && ctrlPnt.y == 0) {
330                        g.drawLine(x1z, y1z, x2z, y2z);
331                    } else {
332                        //the control point of the curve is put another place so that the curve hits the real control point.
333                        QuadCurve2D.Double curve = new QuadCurve2D.Double(x1z, y1z, (4 * ctrlPntViewX - x1z - x2z) / 2, (4 * ctrlPntViewY - y1z - y2z) / 2, x2z, y2z);
334                        g.draw(curve);
335                    }
336    
337                } else {
338    
339                    g.setColor(c1);
340                    g.setStroke(stroke);
341                    g.drawLine(x1z, y1z, x2z, y2z);
342                }
343    
344                if (isDirected) {
345                    ArrowHandler.paint(g, model, zoomFactor);
346                }
347    
348    
349                if (drawEdgeLabels) {
350                    g.setColor(c2);
351                    //            int w = getWidth();
352                    //            int h = getHeight();
353                    GraphPoint ll = model.getLabelLocation();
354                    if (isEdgesCurved)
355                        g.drawString(model.text, ctrlPntViewX + zm(ll.x) + 2 * d, ctrlPntViewY + zm(ll.y) + 2 * d);
356                    else
357                        g.drawString(model.text, zm(edgecenterx + ll.x), zm(edgecentery + ll.y));
358    
359                }
360            }
361        }
362    
363    
364        public void paint(Graphics2D g, VertexModel model, int x, int y, int labelx, int labely, Boolean drawExtras) {
365            GraphPoint size = model.getSize();
366            int w = (int) size.x;
367            int h = (int) size.y;
368            Color color;
369            if (model.getColor() == 0)
370                color = defaultVertexColor;
371            else
372                color = GraphModel.getColor(model.getColor());
373            BasicStroke borderStroke = model.shapeStroke.stroke;
374    
375            if (model.isSelected()) {
376    //                color = selectedColor;
377                color = color.darker().darker();
378            }
379            if (model.getMark()) {
380    //            color = markedColor;
381                borderStroke = markedStroke.stroke;
382            }
383            Color c2;
384            c2 = color.darker().darker();
385    
386            g.setColor(color);
387            g.setStroke(borderStroke);
388            model.shape.fill(g, x, y, w, h);
389    //              if (!vertex.g.view.animation){
390            g.setColor(c2);
391    //        if (model.showBorder)
392            model.shape.draw(g, x, y, w - 1, h - 1);
393    //            if (labelSize.width == 0)
394    //                updateLabelSize();
395    //            g.drawString(model.getLabel(), w / 2 - labelSize.width / 2, h / 2 + labelSize.height / 2);  //dirty formula
396            if (drawVertexLabels) {
397                GraphPoint ll = model.getLabelLocation();
398                g.drawString(model.getLabel(), (int) (labelx + ll.x), (int) (labely + ll.y));  //dirty formula
399            }
400        }
401    
402        public void vertexAdded(VertexModel v) {
403            v.setLabel(v.getId() + "");
404            v.setVertexListener(this);
405            isGraphChanged = true;
406            repaint();
407        }
408    
409        public void vertexRemoved(VertexModel v) {
410            isGraphChanged = true;
411            repaint();
412        }
413    
414        public void edgeAdded(EdgeModel e) {
415            e.setLabel(e.getId());
416            e.setEdgeModelListener(this);
417            isGraphChanged = true;
418            repaint();
419        }
420    
421        public void edgeRemoved(EdgeModel e) {
422            isGraphChanged = true;
423            repaint();
424        }
425    
426        public void repaintGraph() {
427            updateGraphBounds();
428            super.repaintGraph();
429        }
430    
431        public void graphCleared() {
432            updateGraphBounds();
433            isGraphChanged = true;
434            repaint();
435        }
436    
437    
438        public void repaint(VertexModel src) {
439            isGraphChanged = true;
440            updateGraphBounds();
441            repaint();
442        }
443    
444        public void updateSize(VertexModel src, GraphPoint newSize) {
445            updateGraphBounds();
446            isGraphChanged = true;
447            repaint();
448        }
449    
450        public void updateLocation(VertexModel src, GraphPoint newLocation) {
451            updateGraphBounds();
452            isGraphChanged = true;
453            repaint();
454        }
455    
456        public void repaint(EdgeModel src) {
457            isGraphChanged = true;
458            repaint();
459        }
460    
461        public void updateBounds(Rectangle r, EdgeModel src) {
462            isGraphChanged = true;
463    //        repaint();
464        }
465    
466    
467        //graph control listener
468        public void ActionPerformed(GraphEvent event) {
469            blackboard.setData(GraphEvent.EVENT_KEY, event);
470        }
471    
472        public void ActionPerformed(VertexEvent event) {
473            blackboard.setData(VertexEvent.EVENT_KEY, event);
474        }
475    
476        public void ActionPerformed(EdgeEvent event) {
477            blackboard.setData(EdgeEvent.EVENT_KEY, event);
478        }
479    
480        boolean updateBounds = false;
481    
482        private void updateGraphBounds() {
483            updateBounds = true;
484        }
485    
486        boolean isUpdated = false;
487    
488        private void doUpdateGraphBounds() {
489            String s = ((String) blackboard.getData("MoveSelected.moving"));
490            if (s != null && s.equals("yes")) {
491                return;
492            }
493    //        if (!updateBounds)
494    //            return;
495    //        updateBounds = false;
496            Rectangle2D.Double prvb = bounds;
497            bounds = getGraph().getZoomedBounds();
498    //        control.graphBounds = bounds;
499    //        if (!bounds.equals(prvb)) {
500    
501            int dx = 0;
502            int dy = 0;
503            if (bounds.x < 0) {
504                if (bounds.x < prvb.x)
505    
506    //                            System.out.println("x<0");
507    //                if (prvb.x >= 0) {
508    //                    dx = (int) -bounds.x;
509    //                } else {
510                    dx = (int) (prvb.x - bounds.x);
511    //                }
512            }
513            if (bounds.y < 0) {
514                if (bounds.y < prvb.y)
515    
516    //                            System.out.println("y<0");
517    //                if (prvb.y >= 0) {
518    //                    dy = (int) -bounds.y;
519    //                } else {
520                    dy = (int) (prvb.y - bounds.y);
521    //                }
522            }
523    
524            int dw = 0, dh = 0;
525            Dimension vvr = getSize();
526            if (bounds.x + bounds.width > vvr.width) {
527                dw = (int) (prvb.x + prvb.width - bounds.x + bounds.width);
528            }
529            if (bounds.y + bounds.height > vvr.height) {
530                dh = (int) (prvb.y + prvb.height - bounds.y + bounds.height);
531            }
532            minx -= dx;
533            miny -= dy;
534    
535            vvr.width += dx + dw;
536            vvr.height += dy + dh;
537    
538            setIgnoreRepaint(true);
539            ignoreRapaints = true;
540            Rectangle vrect = getVisibleRect();
541            vrect.x += dx;
542            vrect.y += dy;
543    
544            setPreferredSize(vvr);
545            revalidate();
546            if (dx != 0 || dy != 0) {
547                scrollRectToVisible(vrect);
548                isUpdated = true;
549            }
550    
551            revalidate();
552    
553            ignoreRapaints = false;
554            setIgnoreRepaint(false);
555    //                System.out.println("size: " + vvr);
556    //                System.out.println("rect: " + vrect);
557    //                System.out.println("bounds: " + bounds);
558    //            System.out.println("1");
559    
560    //commented for debugging purposes
561    //        } else {
562    //            if (!(bounds.x < 0 || bounds.y < 0)) {
563    ////                            System.out.println("2");
564    //                Rectangle b = bounds.getBounds();
565    //                b.width += Math.abs(b.x);
566    //                b.height += Math.abs(b.y);
567    //                setPreferredSize(b.getSize());
568    //                revalidate();
569    //            }
570    //        }
571            updateBounds = false;
572            ignoreRapaints = false;
573    //        System.out.println("finished");
574        }
575    
576        public void calculateSize() {
577            Rectangle b = bounds.getBounds();
578            if (bounds.x >= 0 && bounds.y >= 0) {
579                b.width += Math.abs(b.x);
580                b.height += Math.abs(b.y);
581            } else {
582                if (bounds.x < 0 && bounds.y >= 0) {
583    
584                }
585            }
586        }
587    
588        public int zm(double v) {
589            return (int) (v * zoomFactor);
590        }
591    }