| /* |
| * 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 ); |
| } |
| } |
| } |
| } |
| |
| } |