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}