![JAR search and dependency download from the Maven repository](/logo.png)
org.graphstream.ui.j2dviewer.renderer.shape.ShapeParts.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gs-ui Show documentation
Show all versions of gs-ui Show documentation
The GraphStream library. With GraphStream you deal with
graphs. Static and Dynamic. You create them from scratch, from a file
or any source. You display and render them.
The newest version!
/*
* Copyright 2006 - 2015
* Stefan Balev
* Julien Baudry
* Antoine Dutot
* Yoann Pigné
* Guilhelm Savin
*
* This file is part of GraphStream .
*
* GraphStream is a library whose purpose is to handle static or dynamic
* graph, create them from scratch, file or any source and display them.
*
* This program is free software distributed under the terms of two licenses, the
* CeCILL-C license that fits European law, and the GNU Lesser General Public
* License. You can use, modify and/ or redistribute the software under the terms
* of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
* URL or under the terms of the GNU LGPL as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
*/
package org.graphstream.ui.j2dviewer.renderer.shape
// This file contains the two most important traits : Area (for nodes and sprites) and Connector
// (for edges). Shapes will either merge one or the other of these traits.
import java.awt._
import java.awt.geom._
import org.graphstream.ui.j2dviewer._
import org.graphstream.ui.j2dviewer.renderer._
import org.graphstream.ui.util._
import org.graphstream.ui.geom._
import org.graphstream.ui.graphicGraph._
import org.graphstream.ui.graphicGraph.stylesheet._
import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants._
import org.graphstream.ui.j2dviewer.renderer.shape.swing.{ShapeDecor, IconAndText}
/** Trait for elements painted inside an area (most nodes and sprites).
*
* This trait manages the size of the area (the size is rectangular, although the area may not
* be), its position, and the automatic fit to the contents, if needed.
*
* As this trait computes the position and size of the shape, it should
* probably be configured first when assembling the configureForGroup
* and configureForElement methods. */
trait Area {
/** The shape position. */
protected val theCenter = new Point2
/** The shape size. */
protected val theSize = new Point2
/** Fit the shape size to its contents? */
protected var fit = false
/** Select the general size for the group. */
protected def configureAreaForGroup(style:Style, camera:Camera) = sizeForGroup(style, camera)
/** Select the general size and position of the shape.
* This is done according to:
* - The style,
* - Eventually the element specific size attribute,
* - Eventually the element contents (decor). */
protected def configureAreaForElement(backend:Backend, camera:Camera, skel:AreaSkeleton, element:GraphicElement, decor:ShapeDecor) {
var pos = camera.getNodeOrSpritePositionGU(element, null)
if(fit) {
val decorSize = decor.size(backend, camera, skel.iconAndText)
if(decorSize._1 == 0 || decorSize._2 == 0)
sizeForElement(element.getStyle, camera, element)
positionAndFit(camera, skel, element, pos.x, pos.y, decorSize._1, decorSize._2)
} else {
sizeForElement(element.getStyle, camera, element)
positionAndFit(camera, skel, element, pos.x, pos.y, 0, 0)
}
}
/** Set the general size of the area according to the style.
* Also look if the style SizeMode says if the element must fit to its contents.
* If so, the configureAreaForElement() method will recompute the size for each
* element according to the contents (shape decoration). */
private[this] def sizeForGroup(style:Style, camera:Camera) {
val w = camera.metrics.lengthToGu( style.getSize, 0 )
val h = if( style.getSize.size > 1 ) camera.metrics.lengthToGu( style.getSize, 1 ) else w
theSize.set(w, h)
fit = (style.getSizeMode == StyleConstants.SizeMode.FIT)
}
/** Try to compute the size of this area according to the given element. */
private[this] def sizeForElement(style:Style, camera:Camera, element:GraphicElement) {
var w = camera.metrics.lengthToGu(style.getSize, 0)
var h = if(style.getSize.size > 1) camera.metrics.lengthToGu(style.getSize, 1) else w
if(style.getSizeMode == StyleConstants.SizeMode.DYN_SIZE) {
var s:AnyRef = element.getAttribute("ui.size")
if(s ne null) {
w = camera.metrics.lengthToGu(StyleConstants.convertValue(s))
h = w;
}
}
theSize.set(w, h)
}
/** Assign a position to the shape according to the element, set the size of the element,
* and update the skeleton of the element. */
private[this] def positionAndFit(camera:Camera, skel:AreaSkeleton, element:GraphicElement, x:Double, y:Double, contentOverallWidth:Double, contentOverallHeight:Double) {
if(skel != null) {
if(contentOverallWidth > 0 && contentOverallHeight > 0)
theSize.set(contentOverallWidth, contentOverallHeight)
skel.theSize.copy(theSize)
}
theCenter.set(x, y)
skel.theCenter.copy(theCenter)
}
}
/** Thing that links to the skeleton of a connector. Allows several traits to have the same
* field (Connector and Decorable for example). */
trait HasSkel {
/** We will use it often, better store it. */
var skel:ConnectorSkeleton = null
}
/**
* Trait for elements painted between two points.
*
* The purpose of this class is to retrieve and store in the skeleton the lines coordinates of an
* edge. This connector can be made of only two points, 4 points when this is a bezier cubic curve
* or more if this is a polyline or a polycurve or a vectorial description.
* The coordinates of these points are stored in a ConnectorSkeleton attribute directly on the edge
* element since several parts of the rendering need to access it (for example, sprites retrieve it
* to follow the correct path when attached to this edge).
*/
trait Connector extends HasSkel {
// Attribute
/** The edge, we will also need it often. */
var theEdge:GraphicEdge = null
/** Width of the connector. */
protected var theSize:Double = 0
/** Overall size of the area at the end of the connector. */
protected var theTargetSize = new Point2(0, 0)
/** Overall sizes of the area at the end of the connector. */
protected var theSourceSize = new Point2(0, 0)
/** Is the connector directed ? */
var isDirected = false
// Command
/** Origin point of the connector. */
def fromPos:Point3 = skel.from
/** First control point. Works only for curves. */
def byPos1:Point3 = if(skel.isCurve) skel(1) else null
/** Second control point. Works only for curves. */
def byPos2:Point3 = if(skel.isCurve) skel(2) else null
/** Destination of the connector. */
def toPos:Point3 = skel.to
def configureConnectorForGroup(style:Style, camera:Camera) = sizeForGroup(style, camera)
def configureConnectorForElement(camera:Camera, element:GraphicEdge, skel:ConnectorSkeleton) {
this.skel = skel
this.theEdge = element
sizeForElement(element.getStyle, camera, element)
endPoints(element.from, element.to, element.isDirected, camera)
if(element.getGroup != null) {
skel.setMulti(element.getGroup.getCount)
}
// XXX TODO there are a lot of cases where we do not need this information.
// It would be good to compute it lazily, only when needed;
// Furthermore, it would be good to be able to update it, only when really
// Changed.
// There is lots of work to be done here, in order to extend the way we get
// the points of the skeleton. Probably a PointVector class that can tell
// when some of its parts changed.
if(element.hasAttribute("ui.points")) {
skel.setPoly(element.getAttribute[AnyRef]("ui.points"))
} else {
positionForLinesAndCurves( skel, element.from.getStyle, element.from,
element.to, element.multi, element.getGroup )
}
}
/** Set the size of the connector using the predefined style. */
private[this] def sizeForGroup(style:Style, camera:Camera) {
theSize = camera.metrics.lengthToGu( style.getSize, 0 )
}
/** Set the size of the connector for this particular `element`. */
private[this] def sizeForElement(style:Style, camera:Camera, element:GraphicElement) {
if(style.getSizeMode == StyleConstants.SizeMode.DYN_SIZE && element.hasAttribute( "ui.size")) {
theSize = camera.metrics.lengthToGu(StyleConstants.convertValue(element.getAttribute("ui.size")))
}
}
/** Define the two end points sizes using the fit size stored in the nodes. */
private[this] def endPoints(from:GraphicNode, to:GraphicNode, directed:Boolean, camera:Camera) {
val fromInfo = from.getAttribute( Skeleton.attributeName ).asInstanceOf[AreaSkeleton]
val toInfo = to.getAttribute( Skeleton.attributeName ).asInstanceOf[AreaSkeleton]
if(fromInfo != null && toInfo != null) {
isDirected = directed
theSourceSize.copy(fromInfo.theSize)
theTargetSize.copy(toInfo.theSize)
} else {
endPoints(from.getStyle, to.getStyle, directed, camera)
}
}
/** Define the two end points sizes (does not use the style nor the fit size). */
private[this] def endPoints(sourceWidth:Double, targetWidth:Double, directed:Boolean) {
theSourceSize.set(sourceWidth, sourceWidth)
theTargetSize.set(targetWidth, targetWidth)
isDirected = directed
}
/** Define the two end points sizes (does not use the style nor the fit size). */
private[this] def endPoints(sourceWidth:Double, sourceHeight:Double, targetWidth:Double, targetHeight:Double, directed:Boolean) {
theSourceSize.set(sourceWidth, sourceHeight)
theTargetSize.set(targetWidth, targetHeight)
isDirected = directed
}
/** Compute the two end points sizes using the style (may not use the fit size). */
private[this] def endPoints(sourceStyle:Style, targetStyle:Style, directed:Boolean, camera:Camera) {
val srcx = camera.metrics.lengthToGu(sourceStyle.getSize, 0)
val srcy = if(sourceStyle.getSize.size > 1) camera.metrics.lengthToGu(sourceStyle.getSize, 1) else srcx
val trgx = camera.metrics.lengthToGu(targetStyle.getSize, 0)
val trgy = if(targetStyle.getSize.size > 1) camera.metrics.lengthToGu(targetStyle.getSize, 1) else trgx
theSourceSize.set(srcx, srcy)
theTargetSize.set(trgx, trgy)
isDirected = directed
}
/** Give the position of the origin and destination points. */
private[this] def positionForLinesAndCurves( skel:ConnectorSkeleton, style:Style, from:GraphicNode, to:GraphicNode) {
positionForLinesAndCurves( skel, style, from, to, 0, null ) }
/**
* Give the position of the origin and destination points, for multi edges.
*
* This only sets the isCurve/isLoop and ctrl1/ctrl2 for multi-edges/edge-loops, if the shape of the
* edge given by the style is also a curve, the make() methods must set these fields (we cannot do it here
* since we do not know the curves). This is important since arrows and sprites can be attached to edges.
*
*/
private[this] def positionForLinesAndCurves( skel:ConnectorSkeleton, style:Style, from:GraphicNode, to:GraphicNode, multi:Int, group:GraphicEdge#EdgeGroup ) {
//skel.points(0).set( xFrom, yFrom )
//skel.points(3).set( xTo, yTo )
if( group != null ) {
if( from == to ) {
positionEdgeLoop(skel, from.getX, from.getY, multi)
} else {
positionMultiEdge(skel, from.getX, from.getY, to.getX, to.getY, multi, group)
}
} else {
if( from == to) {
positionEdgeLoop(skel, from.getX, from.getY, 0)
} else {
// This does not mean the edge is not a curve, this means
// that with what we know actually it is not a curve.
// The style mays indicate a curve.
skel.setLine(from.getX, from.getY, 0, to.getX, to.getY, 0)
// XXX we will have to mutate the skel into a curve later.
}
}
}
/** Define the control points to make the edge a loop. */
private[this] def positionEdgeLoop(skel:ConnectorSkeleton, x:Double, y:Double, multi:Int) {
var m = 1f + multi * 0.2f
val s = ( theTargetSize.x + theTargetSize.y ) / 2
var d = s / 2 * m + 4 * s * m
skel.setLoop(
x, y, 0,
x+d, y, 0,
x, y+d, 0 )
}
/** Define the control points to make this edge a part of a multi-edge. */
private[this] def positionMultiEdge(skel:ConnectorSkeleton, x1:Double, y1:Double, x2:Double, y2:Double, multi:Int, group:GraphicEdge#EdgeGroup) {
var vx = ( x2 - x1 )
var vy = ( y2 - y1 )
var vx2 = ( vy ) * 0.6
var vy2 = ( -vx ) * 0.6
val gap = 0.2
var ox = 0.0
var oy = 0.0
val f = ( ( 1 + multi ) / 2 ) * gap // (1+multi)/2 must be done on integers.
vx *= 0.2
vy *= 0.2
val main = group.getEdge( 0 )
val edge = group.getEdge( multi )
if( group.getCount %2 == 0 ) {
ox = vx2 * (gap/2)
oy = vy2 * (gap/2)
if( edge.from ne main.from ) { // Edges are in the same direction.
ox = - ox
oy = - oy
}
}
vx2 *= f
vy2 *= f
var xx1 = x1 + vx
var yy1 = y1 + vy
var xx2 = x2 - vx
var yy2 = y2 - vy
val m = multi + ( if( edge.from eq main.from ) 0 else 1 )
if( m % 2 == 0 ) {
xx1 += ( vx2 + ox )
yy1 += ( vy2 + oy )
xx2 += ( vx2 + ox )
yy2 += ( vy2 + oy )
} else {
xx1 -= ( vx2 - ox )
yy1 -= ( vy2 - oy )
xx2 -= ( vx2 - ox )
yy2 -= ( vy2 - oy )
}
skel.setCurve(
x1, y1, 0,
xx1, yy1, 0,
xx2, yy2, 0,
x2, y2, 0 )
}
}
trait OnConnector {
/** The connector we are attached to. */
protected var theConnector:Connector = null
/** XXX must call this method explicitly in the renderer !!! bad !!! XXX */
def theConnectorYoureAttachedTo(connector:Connector) { theConnector = connector }
}
/** Some areas are attached to a connector (sprites). */
trait AreaOnConnector extends Area with OnConnector {
/** The edge represented by the connector.. */
protected var theEdge:GraphicEdge = null
protected def configureAreaOnConnectorForGroup(style:Style, camera:Camera) {
sizeForEdgeArrow(style, camera)
}
protected def configureAreaOnConnectorForElement(edge:GraphicEdge, style:Style, camera:Camera) {
connector(edge)
theCenter.set(edge.to.getX, edge.to.getY)
}
private def connector(edge:GraphicEdge) { theEdge = edge }
private def sizeForEdgeArrow(style:Style, camera:Camera) {
val w = camera.metrics.lengthToGu(style.getArrowSize, 0)
val h = if(style.getArrowSize.size > 1) camera.metrics.lengthToGu(style.getArrowSize, 1) else w
theSize.set(w, h)
}
}
/** Trait for all shapes that points at a direction. */
trait Orientable {
/** The shape orientation. */
var orientation:StyleConstants.SpriteOrientation = null
/** The shape target. */
var target = new Point3
/** Configure the orientation mode for the group according to the style. */
protected def configureOrientableForGroup(style:Style, camera:Camera) { orientation = style.getSpriteOrientation }
/** Compute the orientation vector for the given element according to the orientation mode. */
protected def configureOrientableForElement(camera:Camera, sprite:GraphicSprite) {
sprite.getAttachment match {
case gn:GraphicNode => {
sprite.getStyle.getSpriteOrientation match {
case SpriteOrientation.NONE => { target.set(0, 0) }
case SpriteOrientation.FROM => { target.set(gn.getX, gn.getY) }
case SpriteOrientation.TO => { target.set(gn.getX, gn.getY) }
case SpriteOrientation.PROJECTION => { target.set(gn.getX, gn.getY) }
case _ => {}
}
}
case ge:GraphicEdge => {
sprite.getStyle.getSpriteOrientation match {
case SpriteOrientation.NONE => { target.set(0, 0) }
case SpriteOrientation.FROM => { target.set(ge.from.getX, ge.from.getY) }
case SpriteOrientation.TO => { target.set(ge.to.getX, ge.to.getY) }
case SpriteOrientation.PROJECTION => {
val ei = ge.getAttribute[ConnectorSkeleton](Skeleton.attributeName)
if(ei != null)
ei.pointOnShape(sprite.getX, target)
else setTargetOnLineEdge(camera, sprite, ge)
}
case _ => {}
}
}
case _ => { orientation = SpriteOrientation.NONE }
}
}
private[this] def setTargetOnLineEdge(camera:Camera, sprite:GraphicSprite, ge:GraphicEdge) {
val dir = new Vector2(ge.to.getX-ge.from.getX, ge.to.getY-ge.from.getY)
dir.scalarMult(sprite.getX)
target.set(ge.from.getX + dir.x, ge.from.getY + dir.y)
}
}
/** Trait for shapes that can be decorated by an icon and/or a text. */
trait Decorable extends HasSkel {
/** The string of text of the contents. */
var text:String = null
/** The text and icon. */
var theDecor:ShapeDecor = null
/** Paint the decorations (text and icon). */
def decorArea(backend:Backend, camera:Camera, iconAndText:IconAndText, element:GraphicElement, shape:java.awt.Shape ) {
var visible = true
if( element != null ) visible = camera.isTextVisible( element )
if( theDecor != null && visible ) {
val bounds = shape.getBounds2D
theDecor.renderInside(backend, camera, iconAndText, bounds.getMinX, bounds.getMinY, bounds.getMaxX, bounds.getMaxY )
}
}
def decorConnector(backend:Backend, camera:Camera, iconAndText:IconAndText, element:GraphicElement, shape:java.awt.Shape ) {
var visible = true
if( element != null ) visible = camera.isTextVisible( element )
if( theDecor != null && visible ) {
element match {
case edge:GraphicEdge => {
if((skel ne null) && (skel.isCurve)) {
theDecor.renderAlong(backend, camera, iconAndText, skel)
} else {
theDecor.renderAlong(backend, camera, iconAndText, edge.from.x, edge.from.y, edge.to.x, edge.to.y)
}
}
case _ => {
val bounds = shape.getBounds2D
theDecor.renderAlong(backend, camera, iconAndText, bounds.getMinX, bounds.getMinY, bounds.getMaxX, bounds.getMaxY )
}
}
}
}
/** Configure all the static parts needed to decor the shape. */
protected def configureDecorableForGroup( style:Style, camera:Camera ) {
/*if( theDecor == null )*/ theDecor = ShapeDecor( style )
}
/** Setup the parts of the decor specific to each element. */
protected def configureDecorableForElement(backend:Backend, camera:Camera, element:GraphicElement, skel:Skeleton) {
text = element.label
if( skel != null ) {
val style = element.getStyle
skel.iconAndText = ShapeDecor.iconAndText( style, camera, element )
if( style.getIcon != null && style.getIcon.equals( "dynamic" ) && element.hasAttribute( "ui.icon" ) ) {
val url = element.getLabel("ui.icon").toString
skel.iconAndText.setIcon(backend, url)
}
skel.iconAndText.setText(backend, element.label)
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy