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.shapes;
033
034import java.awt.Color;
035import java.awt.Graphics2D;
036import java.awt.geom.Path2D;
037
038import org.graphstream.ui.graphicGraph.GraphicEdge;
039import org.graphstream.ui.graphicGraph.GraphicNode;
040import org.graphstream.ui.graphicGraph.StyleGroup;
041import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.StrokeMode;
042import org.graphstream.ui.swingViewer.util.GraphMetrics;
043
044public class Arrow extends Shape {
045        protected Color fillColor = Color.BLACK;
046
047        protected Color strokeColor = Color.BLACK;
048
049        protected int lengthGu = 0;
050
051        protected int widthGu = 0;
052
053        protected double x, y;
054
055        protected Path2D.Float path = new Path2D.Float();
056
057        public void setArrowLengthGu(int lengthGu) {
058                this.lengthGu = lengthGu;
059        }
060
061        public void setArrowWidthGu(int widthGu) {
062                this.widthGu = widthGu;
063        }
064
065        public void setFillColor(Color color) {
066                fillColor = color;
067        }
068
069        public void setStrokeColor(Color color) {
070                strokeColor = color;
071        }
072
073        @Override
074        public void renderFill(Graphics2D g, GraphMetrics metrics) {
075                g.setColor(fillColor);
076                g.fill(path);
077        }
078
079        @Override
080        public void renderStroke(Graphics2D g, GraphMetrics metrics) {
081                g.setColor(strokeColor);
082                g.draw(path);
083        }
084
085        // Utility
086
087        protected void setPositionAndShape(GraphicEdge edge, GraphMetrics metrics) {
088                // Compute the direction vector and some lengths.
089
090                x = edge.to.x;
091                y = edge.to.y;
092                double vx = x - edge.from.x;
093                double vy = y - edge.from.y;
094                double off = evalTargetRadius(edge, metrics);
095
096                // Normalise the vectors.
097
098                double d = (double) Math.sqrt(vx * vx + vy * vy);
099
100                vx /= d;
101                vy /= d;
102
103                // Choose an arrow "length".
104
105                x -= vx * off;
106                y -= vy * off;
107
108                setShapeAt(x, y, vx, vy);
109        }
110
111        /**
112         * Compute the shape of the arrow.
113         * 
114         * @param x
115         *            Point at which the edge crosses the node shape.
116         * @param y
117         *            Point at which the edge crosses the node shape.
118         * @param dx
119         *            The arrow vector (and length).
120         * @param dy
121         *            The arrow vector (and length).
122         */
123        protected void setShapeAt(double x, double y, double dx, double dy) {
124                // Compute the edge vector (1) and the perpendicular vector (2).
125
126                double dx2 = dy;
127                double dy2 = -dx;
128
129                // Normalise the vectors.
130
131                double d2 = (double) Math.sqrt(dx2 * dx2 + dy2 * dy2);
132
133                dx2 /= d2;
134                dy2 /= d2;
135
136                // Choose an arrow "width".
137
138                dx2 *= widthGu;
139                dy2 *= widthGu;
140
141                // Create a polygon.
142
143                path.reset();
144                path.moveTo(x, y);
145                path.lineTo(x - dx + dx2, y - dy + dy2);
146                path.lineTo(x - dx - dx2, y - dy - dy2);
147                path.closePath();
148        }
149
150        /**
151         * Evaluate the position of the arrow to avoid putting it above or under the
152         * target node.
153         * 
154         * @param edge
155         *            The edge.
156         * @param metrics
157         *            The metrics.
158         * @return The length from the node centre along the edge to position the
159         *         arrow.
160         */
161        protected double evalTargetRadius(GraphicEdge edge, GraphMetrics metrics) {
162                GraphicNode target = edge.to;
163                StyleGroup group = target.getStyle();
164                double w = metrics.lengthToGu(group.getSize(), 0);
165                double h = group.getSize().size() > 1 ? metrics.lengthToGu(
166                                group.getSize(), 1) : w;
167
168                if (w == h) {
169                        double b = group.getStrokeMode() != StrokeMode.NONE ? metrics
170                                        .lengthToGu(group.getStrokeWidth()) : 0;
171                        return ((w / 2) + b);
172                } else {
173                        return evalEllipseRadius(edge, w, h);
174                }
175        }
176
177        /**
178         * Compute the length of a vector along the edge from the ellipse centre to
179         * the intersection between the edge and the ellipse.
180         * 
181         * @param edge
182         *            The edge representing the vector.
183         * @param w
184         *            The ellipse first radius (width/2).
185         * @param h
186         *            The ellipse second radius (height/2).
187         * @return The length of the radius along the edge vector.
188         */
189        protected double evalEllipseRadius(GraphicEdge edge, double w, double h) {
190                // Vector of the entering edge.
191
192                double dx;
193                double dy;
194
195                dx = edge.to.x - edge.from.x;
196                dy = edge.to.y - edge.from.y;
197
198                // The entering edge must be deformed by the ellipse ratio to find the
199                // correct angle.
200
201                dy *= (w / h); // I searched a lot to find this line was missing ! Tsu !
202                                                // This comment is in memory of this long search.
203
204                // Find the angle of the entering vector with (1,0).
205
206                double d = (double) Math.sqrt(dx * dx + dy * dy);
207                double a = dx / d;
208
209                // Compute the coordinates at which the entering vector and the ellipse
210                // cross.
211
212                a = (double) Math.acos(a);
213                dx = (double) Math.cos(a) * w;
214                dy = (double) Math.sin(a) * h;
215
216                // The distance from the ellipse centre to the crossing point of the
217                // ellipse and
218                // vector. Yo !
219
220                return Math.sqrt(dx * dx + dy * dy);
221        }
222}