blob: ac395dc6852ba226555fdff03d559aa44573d620 [file] [log] [blame]
/*
* Copyright (c) 2009, Tony Kohar. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zest.envisage.graph;
import java.awt.Color;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import org.apache.zest.envisage.event.LinkEvent;
import prefuse.Constants;
import prefuse.Visualization;
import prefuse.action.Action;
import prefuse.action.ActionList;
import prefuse.action.ItemAction;
import prefuse.action.RepaintAction;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.animate.LocationAnimator;
import prefuse.action.animate.QualityControlAnimator;
import prefuse.action.animate.VisibilityAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.FontAction;
import prefuse.action.filter.FisheyeTreeFilter;
import prefuse.action.layout.CollapsedSubtreeLayout;
import prefuse.action.layout.graph.NodeLinkTreeLayout;
import prefuse.activity.SlowInSlowOutPacer;
import prefuse.controls.ControlAdapter;
import prefuse.controls.FocusControl;
import prefuse.controls.PanControl;
import prefuse.controls.WheelZoomControl;
import prefuse.controls.ZoomControl;
import prefuse.controls.ZoomToFitControl;
import prefuse.data.Graph;
import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.data.tuple.TupleSet;
import prefuse.render.AbstractShapeRenderer;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.GraphicsLib;
import prefuse.util.PrefuseLib;
import prefuse.util.display.DisplayLib;
import prefuse.visual.VisualItem;
import prefuse.visual.sort.TreeDepthItemSorter;
public final class TreeGraphDisplay
extends GraphDisplay
{
static final String FILTER_ACTION = "filter";
static final String REPAINT_ACTION = "repaint";
static final String FULL_PAINT_ACTION = "fullPaint";
static final String ANIMATE_PAINT_ACTION = "animatePaint";
static final String ANIMATE_ACTION = "animate";
static final String LAYOUT_ACTION = "layout";
static final String SUB_LAYOUT_ACTION = "subLayout";
static final String AUTO_ZOOM_ACTION = "autoZoom";
private final LabelRenderer nodeRenderer;
private final EdgeRenderer edgeRenderer;
private final EdgeRenderer usesRenderer;
private final int orientation = Constants.ORIENT_LEFT_RIGHT;
public TreeGraphDisplay()
{
super( new Visualization() );
Color BACKGROUND = Color.WHITE;
Color FOREGROUND = Color.BLACK;
setBackground( BACKGROUND );
setForeground( FOREGROUND );
nodeRenderer = new LabelRenderer( NAME_LABEL );
nodeRenderer.setRenderType( AbstractShapeRenderer.RENDER_TYPE_FILL );
nodeRenderer.setHorizontalAlignment( Constants.LEFT );
nodeRenderer.setRoundedCorner( 8, 8 );
edgeRenderer = new EdgeRenderer( Constants.EDGE_TYPE_CURVE );
usesRenderer = new EdgeRenderer( Constants.EDGE_TYPE_CURVE, Constants.EDGE_ARROW_FORWARD );
Predicate edgesPredicate = (Predicate) ExpressionParser.parse( "ingroup('graph.edges') AND [" + USES_EDGES + "]==false", true );
Predicate usesPredicate = (Predicate) ExpressionParser.parse( "ingroup('graph.edges') AND [" + USES_EDGES + "]==true", true );
DefaultRendererFactory rf = new DefaultRendererFactory( nodeRenderer );
rf.add( edgesPredicate, edgeRenderer );
rf.add( usesPredicate, usesRenderer );
m_vis.setRendererFactory( rf );
// colors
ItemAction nodeColor = new NodeColorAction( GRAPH_NODES );
ItemAction textColor = new ColorAction( GRAPH_NODES, VisualItem.TEXTCOLOR, ColorLib.rgb( 0, 0, 0 ) );
m_vis.putAction( "textColor", textColor );
ItemAction edgeColor = new ColorAction( GRAPH_EDGES, edgesPredicate, VisualItem.STROKECOLOR, ColorLib.rgb( 200, 200, 200 ) );
ItemAction usesColor = new ColorAction( GRAPH_EDGES, usesPredicate, VisualItem.STROKECOLOR, ColorLib.rgb( 255, 100, 100 ) );
ItemAction usesArrow = new ColorAction( GRAPH_EDGES, usesPredicate, VisualItem.FILLCOLOR, ColorLib.rgb( 255, 100, 100 ) );
// quick repaint
ActionList repaint = new ActionList();
repaint.add( nodeColor );
repaint.add( new RepaintAction() );
m_vis.putAction( REPAINT_ACTION, repaint );
// full paint
ActionList fullPaint = new ActionList();
fullPaint.add( nodeColor );
m_vis.putAction( FULL_PAINT_ACTION, fullPaint );
// animate paint change
//ActionList animatePaint = new ActionList( 0 );
ActionList animatePaint = new ActionList();
animatePaint.add( new ColorAnimator( GRAPH_NODES ) );
animatePaint.add( new RepaintAction() );
m_vis.putAction( ANIMATE_PAINT_ACTION, animatePaint );
// create the GRAPH layout action
NodeLinkTreeLayout treeLayout = new NodeLinkTreeLayout( GRAPH, orientation, 40, 0, 8 );
treeLayout.setLayoutAnchor( new Point2D.Double( 25, 300 ) );
m_vis.putAction( LAYOUT_ACTION, treeLayout );
CollapsedSubtreeLayout subLayout = new CollapsedSubtreeLayout( GRAPH, orientation );
m_vis.putAction( SUB_LAYOUT_ACTION, subLayout );
// create the filtering and layout
ActionList filter = new ActionList();
filter.add( new ExtendedFisheyeTreeFilter( GRAPH, 2 ) );
filter.add( new FontAction( GRAPH_NODES, FontLib.getFont( "Tahoma", 14 ) ) );
filter.add( treeLayout );
filter.add( subLayout );
filter.add( textColor );
filter.add( nodeColor );
filter.add( edgeColor );
filter.add( usesColor );
filter.add( usesArrow );
m_vis.putAction( FILTER_ACTION, filter );
// animated transition
AutoPanAction autoPan = new AutoPanAction();
ActionList animate = new ActionList( 750 );
animate.setPacingFunction( new SlowInSlowOutPacer() );
animate.add( autoPan );
animate.add( new QualityControlAnimator() );
animate.add( new VisibilityAnimator( GRAPH ) );
animate.add( new LocationAnimator( GRAPH_NODES ) );
animate.add( new ColorAnimator( GRAPH_NODES ) );
animate.add( new RepaintAction() );
m_vis.putAction( ANIMATE_ACTION, animate );
m_vis.alwaysRunAfter( FILTER_ACTION, ANIMATE_ACTION );
m_vis.putAction( AUTO_ZOOM_ACTION, new AutoZoomAction() );
// initialize the display
setItemSorter( new TreeDepthItemSorter() );
addControlListener( new ZoomToFitControl() );
addControlListener( new ZoomControl() );
addControlListener( new WheelZoomControl() );
addControlListener( new PanControl() );
addControlListener( new FocusControl( 1, FILTER_ACTION ) );
addControlListener( new ItemSelectionControl() );
// set orientation
nodeRenderer.setHorizontalAlignment( Constants.LEFT );
edgeRenderer.setHorizontalAlignment1( Constants.RIGHT );
edgeRenderer.setHorizontalAlignment2( Constants.LEFT );
edgeRenderer.setVerticalAlignment1( Constants.CENTER );
edgeRenderer.setVerticalAlignment2( Constants.CENTER );
usesRenderer.setHorizontalAlignment1( Constants.CENTER );
usesRenderer.setHorizontalAlignment2( Constants.CENTER );
usesRenderer.setVerticalAlignment1( Constants.BOTTOM );
usesRenderer.setVerticalAlignment2( Constants.CENTER );
NodeLinkTreeLayout rtl = (NodeLinkTreeLayout) m_vis.getAction( LAYOUT_ACTION );
CollapsedSubtreeLayout stl = (CollapsedSubtreeLayout) m_vis.getAction( SUB_LAYOUT_ACTION );
rtl.setOrientation( orientation );
stl.setOrientation( orientation );
}
@Override
public void run( Graph graph )
{
m_vis.add( GRAPH, graph );
run();
m_vis.run( AUTO_ZOOM_ACTION );
// disable edges interactive
m_vis.setInteractive( GRAPH_EDGES, null, false );
}
@Override
public void run()
{
m_vis.run( FILTER_ACTION );
}
/**
* select the specified object
*
* @param object the object to select eg: Descriptor
*/
@Override
public void setSelectedValue( Object object )
{
if( object == null )
{
return;
}
VisualItem item = null;
Iterator iter = m_vis.items( GRAPH_NODES );
while( iter.hasNext() )
{
VisualItem tItem = (VisualItem) iter.next();
Object tObj = tItem.get( USER_OBJECT );
if( tObj.equals( object ) )
{
item = tItem;
break;
}
}
if( item != null )
{
TupleSet ts = m_vis.getFocusGroup( Visualization.FOCUS_ITEMS );
ts.setTuple( item );
m_vis.run( FILTER_ACTION );
}
}
private class AutoZoomAction
extends Action
{
@Override
public void run( double frac )
{
int duration = 20;
int margin = 50;
Visualization vis = getVisualization();
Rectangle2D bounds = vis.getBounds( Visualization.ALL_ITEMS );
GraphicsLib.expand( bounds, margin + (int) ( 1 / getScale() ) );
DisplayLib.fitViewToBounds( TreeGraphDisplay.this, bounds, duration );
}
}
private class AutoPanAction
extends Action
{
private final Point2D m_start = new Point2D.Double();
private final Point2D m_end = new Point2D.Double();
private final Point2D m_cur = new Point2D.Double();
private final int m_bias = 150;
@Override
public void run( double frac )
{
TupleSet ts = m_vis.getFocusGroup( Visualization.FOCUS_ITEMS );
if( ts.getTupleCount() == 0 )
{
return;
}
if( frac == 0.0 )
{
int xbias, ybias = 0;
xbias = m_bias;
switch( orientation )
{
case Constants.ORIENT_LEFT_RIGHT:
break;
case Constants.ORIENT_RIGHT_LEFT:
xbias = -m_bias;
break;
case Constants.ORIENT_TOP_BOTTOM:
ybias = m_bias;
break;
case Constants.ORIENT_BOTTOM_TOP:
ybias = -m_bias;
break;
}
VisualItem vi = (VisualItem) ts.tuples().next();
m_cur.setLocation( getWidth() / 2, getHeight() / 2 );
getAbsoluteCoordinate( m_cur, m_start );
m_end.setLocation( vi.getX() + xbias, vi.getY() + ybias );
}
else
{
m_cur.setLocation( m_start.getX() + frac * ( m_end.getX() - m_start.getX() ),
m_start.getY() + frac * ( m_end.getY() - m_start.getY() ) );
panToAbs( m_cur );
}
}
}
private static class NodeColorAction
extends ColorAction
{
private NodeColorAction( String group )
{
super( group, VisualItem.FILLCOLOR );
}
@Override
public int getColor( VisualItem item )
{
if( m_vis.isInGroup( item, Visualization.SEARCH_ITEMS ) )
{
return ColorLib.rgb( 255, 190, 190 );
}
else if( m_vis.isInGroup( item, Visualization.FOCUS_ITEMS ) )
{
return ColorLib.rgb( 198, 229, 229 );
}
else if( item.getDOI() > -1 )
{
return ColorLib.rgb( 164, 193, 193 );
}
else
{
return ColorLib.rgba( 255, 255, 255, 0 );
}
}
}
private class ItemSelectionControl
extends ControlAdapter
{
@Override
public final void itemClicked( VisualItem anItem, MouseEvent anEvent )
{
if( !anItem.canGet( USER_OBJECT, Object.class ) )
{
return;
}
Object object = anItem.get( USER_OBJECT );
LinkEvent evt = new LinkEvent( TreeGraphDisplay.this, object );
fireLinkActivated( evt );
}
}
private static class ExtendedFisheyeTreeFilter
extends FisheyeTreeFilter
{
private ExtendedFisheyeTreeFilter( String group, int distance )
{
super( group, distance );
}
@Override
public void run( double frac )
{
super.run( frac );
// set uses_edges always visible
Iterator items = m_vis.items( GRAPH_EDGES );
while( items.hasNext() )
{
VisualItem item = (VisualItem) items.next();
if( item.getBoolean( USES_EDGES ) )
{
PrefuseLib.updateVisible( item, true );
}
}
}
}
}