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.ui.swingViewer.basicRenderer;
033
034import java.awt.BasicStroke;
035import java.awt.Color;
036import java.awt.Graphics2D;
037import java.awt.geom.Line2D;
038import java.awt.geom.Path2D;
039
040import org.graphstream.ui.geom.Vector2;
041import org.graphstream.ui.graphicGraph.GraphicEdge;
042import org.graphstream.ui.graphicGraph.GraphicElement;
043import org.graphstream.ui.graphicGraph.GraphicNode;
044import org.graphstream.ui.graphicGraph.StyleGroup;
045import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants;
046import org.graphstream.ui.graphicGraph.stylesheet.Values;
047import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.ArrowShape;
048import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.FillMode;
049import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.SizeMode;
050import org.graphstream.ui.swingViewer.util.Camera;
051
052public class EdgeRenderer extends ElementRenderer {
053        protected Line2D shape = new Line2D.Double();
054
055        protected double width = 1;
056
057        protected double arrowLength = 0;
058
059        protected double arrowWidth = 0;
060
061        @Override
062        protected void setupRenderingPass(StyleGroup group, Graphics2D g,
063                        Camera camera) {
064                configureText(group, camera);
065        }
066
067        @Override
068        protected void pushDynStyle(StyleGroup group, Graphics2D g, Camera camera,
069                        GraphicElement element) {
070                Color color = group.getFillColor(0);
071
072                if (element != null && group.getFillMode() == FillMode.DYN_PLAIN)
073                        color = interpolateColor(group, element);
074
075                g.setColor(color);
076
077                if (group.getSizeMode() == SizeMode.DYN_SIZE) {
078                        width = camera.getMetrics().lengthToGu(
079                                        StyleConstants
080                                                        .convertValue(element.getAttribute("ui.size")));
081                        // width = camera.getMetrics().lengthToGu( (double)
082                        // element.getNumber( "ui.size" ), Units.PX );
083
084                        g.setStroke(new BasicStroke((float) width, BasicStroke.CAP_BUTT,
085                                        BasicStroke.JOIN_BEVEL));
086                }
087        }
088
089        @Override
090        protected void pushStyle(StyleGroup group, Graphics2D g, Camera camera) {
091                width = camera.getMetrics().lengthToGu(group.getSize(), 0);
092                arrowLength = camera.getMetrics().lengthToGu(group.getArrowSize(), 0);
093                arrowWidth = camera.getMetrics().lengthToGu(group.getArrowSize(),
094                                group.getArrowSize().size() > 1 ? 1 : 0);
095
096                g.setColor(group.getFillColor(0));
097                g.setStroke(new BasicStroke((float) width, BasicStroke.CAP_BUTT,
098                                BasicStroke.JOIN_BEVEL));
099        }
100
101        @Override
102        protected void elementInvisible(StyleGroup group, Graphics2D g,
103                        Camera camera, GraphicElement element) {
104        }
105
106        @Override
107        protected void renderElement(StyleGroup group, Graphics2D g, Camera camera,
108                        GraphicElement element) {
109                GraphicEdge edge = (GraphicEdge) element;
110                GraphicNode node0 = (GraphicNode) edge.getNode0();
111                GraphicNode node1 = (GraphicNode) edge.getNode1();
112
113                shape.setLine(node0.x, node0.y, node1.x, node1.y);
114                g.draw(shape);
115                renderArrow(group, g, camera, edge);
116                renderText(group, g, camera, element);
117        }
118
119        protected void renderArrow(StyleGroup group, Graphics2D g, Camera camera,
120                        GraphicEdge edge) {
121                if (edge.isDirected() && arrowWidth > 0 && arrowLength > 0) {
122                        if (group.getArrowShape() != ArrowShape.NONE) {
123                                Path2D shape = new Path2D.Double();
124                                GraphicNode node0 = (GraphicNode) edge.getNode0();
125                                GraphicNode node1 = (GraphicNode) edge.getNode1();
126                                double off = evalEllipseRadius(edge, node0, node1, camera);
127                                Vector2 theDirection = new Vector2(node1.getX() - node0.getX(),
128                                                node1.getY() - node0.getY());
129
130                                theDirection.normalize();
131
132                                double x = node1.x - (theDirection.data[0] * off);
133                                double y = node1.y - (theDirection.data[1] * off);
134                                Vector2 perp = new Vector2(theDirection.data[1],
135                                                -theDirection.data[0]);
136
137                                perp.normalize();
138                                theDirection.scalarMult(arrowLength);
139                                perp.scalarMult(arrowWidth);
140
141                                // Create a polygon.
142
143                                shape.reset();
144                                shape.moveTo(x, y);
145                                shape.lineTo(x - theDirection.data[0] + perp.data[0], y
146                                                - theDirection.data[1] + perp.data[1]);
147                                shape.lineTo(x - theDirection.data[0] - perp.data[0], y
148                                                - theDirection.data[1] - perp.data[1]);
149                                shape.closePath();
150
151                                g.fill(shape);
152                        }
153                }
154        }
155
156        protected double evalEllipseRadius(GraphicEdge edge, GraphicNode node0,
157                        GraphicNode node1, Camera camera) {
158                Values size = node1.getStyle().getSize();
159                double w = camera.getMetrics().lengthToGu(size.get(0), size.getUnits());
160                double h = size.size() > 1 ? camera.getMetrics().lengthToGu(
161                                size.get(1), size.getUnits()) : w;
162
163                w /= 2;
164                h /= 2;
165                                
166                if (w == h)
167                        return w; // Welcome simplification for circles ...
168
169                // Vector of the entering edge.
170                double dx = node1.getX() - node0.getX();
171                double dy = node1.getY() - node0.getY();
172
173                // The entering edge must be deformed by the ellipse ratio to find the
174                // correct angle.
175
176                dy *= (w / h);
177
178                // Find the angle of the entering vector with (1,0).
179
180                double d = Math.sqrt(dx * dx + dy * dy);
181                double a = dx / d;
182
183                // Compute the coordinates at which the entering vector and the ellipse
184                // cross.
185
186                a = Math.acos(a);
187                dx = Math.cos(a) * w;
188                dy = Math.sin(a) * h;
189
190                // The distance from the ellipse center to the crossing point of the
191                // ellipse and vector:
192
193                return Math.sqrt(dx * dx + dy * dy);
194        }
195}