Created
          September 13, 2012 02:41 
        
      - 
      
- 
        Save ulmangt/3711500 to your computer and use it in GitHub Desktop. 
    A Glimpse example demonstrating dragging PolygonPainter polygons
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | /* | |
| * Copyright (c) 2012, Metron, Inc. | |
| * All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are met: | |
| * * Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * * Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * * Neither the name of Metron, Inc. nor the | |
| * names of its contributors may be used to endorse or promote products | |
| * derived from this software without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| * DISCLAIMED. IN NO EVENT SHALL METRON, INC. BE LIABLE FOR ANY | |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| import java.awt.geom.Path2D; | |
| import java.awt.geom.Path2D.Float; | |
| import java.awt.geom.PathIterator; | |
| import java.awt.geom.Rectangle2D; | |
| import java.util.Collection; | |
| import java.util.HashMap; | |
| import java.util.Map; | |
| import com.metsci.glimpse.event.mouse.GlimpseMouseAdapter; | |
| import com.metsci.glimpse.event.mouse.GlimpseMouseEvent; | |
| import com.metsci.glimpse.event.mouse.GlimpseMouseMotionListener; | |
| import com.metsci.glimpse.examples.Example; | |
| import com.metsci.glimpse.examples.basic.FunctionPlotExample; | |
| import com.metsci.glimpse.layout.GlimpseAxisLayout2D; | |
| import com.metsci.glimpse.layout.GlimpseLayout; | |
| import com.metsci.glimpse.layout.GlimpseLayoutProvider; | |
| import com.metsci.glimpse.painter.shape.PolygonPainter; | |
| import com.metsci.glimpse.painter.track.Point; | |
| import com.metsci.glimpse.support.color.GlimpseColor; | |
| import com.metsci.glimpse.util.quadtree.QuadTreeXys; | |
| public class PolygonDragExample implements GlimpseLayoutProvider | |
| { | |
| public static void main( String[] args ) throws Exception | |
| { | |
| Example.showWithSwing( new PolygonDragExample( ) ); | |
| } | |
| // the maximum bounding box side size for all polygons | |
| // if this value is too small, the QuadTree check may miss polygons | |
| static float MAX_POLY_BOUND_BOX = 0.0f; | |
| QuadTreeXys<Point> quadTree = new QuadTreeXys<Point>( 0 ); | |
| Map<Integer, Polygon> polygonMap = new HashMap<Integer, Polygon>( ); | |
| PolygonPainter painter = new PolygonPainter( ); | |
| Polygon currentSelectedPolygon = null; | |
| Polygon initialSelectedPolygon = null; | |
| double initialX; | |
| double initialY; | |
| int id = 1; | |
| @Override | |
| public GlimpseLayout getLayout( ) | |
| { | |
| final GlimpseAxisLayout2D layout = ( GlimpseAxisLayout2D ) new FunctionPlotExample( ).getLayout( ); | |
| layout.addPainter( painter ); | |
| addPolygon( new float[] { 0f, 0f, 1f, 1f }, new float[] { 0f, 1f, 1f, 0f } ); | |
| addPolygon( new float[] { 5f, 5f, 6f, 6f, 5.5f }, new float[] { 5f, 6f, 6f, 5f, 1f } ); | |
| addPolygon( new float[] { -2f, -2f, -1f }, new float[] { -3f, 0f, -3f } ); | |
| // create a dummy layout which sits on top of the plot so that it will receive mouse events first | |
| GlimpseAxisLayout2D mouseListenerLayer = new GlimpseAxisLayout2D( ); | |
| // have the layer not consume events by default (however, we will call GlimpseMouseEvent.setHandled( true ) | |
| // when the user clicks on a polygon which will prevent events from being passed on to the plot) | |
| mouseListenerLayer.setEventConsumer( false ); | |
| layout.addLayout( mouseListenerLayer ); | |
| // add a listener which reports when the mouse moves | |
| // if the mouse button is held down under an icon, the icon is moved | |
| // so that it appears as if it is being dragged by the mouse | |
| mouseListenerLayer.addGlimpseMouseMotionListener( new GlimpseMouseMotionListener( ) | |
| { | |
| @Override | |
| public void mouseMoved( final GlimpseMouseEvent e ) | |
| { | |
| // if a button is being held down and a polygon has been selected | |
| // then drag the polygon instead of translating the plot | |
| if ( e.isAnyButtonDown( ) && initialSelectedPolygon != null ) | |
| { | |
| // get the coordinates of the mouse event in axis coordinates ( e.getX( ) returns pixel coordinates ) | |
| double x = e.getAxisCoordinatesX( ); | |
| double y = e.getAxisCoordinatesY( ); | |
| float dx = ( float ) ( x - initialX ); | |
| float dy = ( float ) ( y - initialY ); | |
| int id = initialSelectedPolygon.getId( ); | |
| currentSelectedPolygon = initialSelectedPolygon.newTranslatedPolygon( dx, dy ); | |
| polygonMap.put( id, currentSelectedPolygon ); | |
| painter.clearGroup( id ); | |
| painter.addPolygon( id, id, currentSelectedPolygon.getVerticesX( ), currentSelectedPolygon.getVerticesY( ), 0 ); | |
| e.setHandled( true ); | |
| } | |
| } | |
| } ); | |
| // add another mouse listener which reports when the mouse button is pressed | |
| // this is used to lock the axes if the mouse is under an icon | |
| mouseListenerLayer.addGlimpseMouseListener( new GlimpseMouseAdapter( ) | |
| { | |
| @Override | |
| public void mousePressed( GlimpseMouseEvent e ) | |
| { | |
| // get the coordinates of the mouse event in axis coordinates ( e.getX( ) returns pixel coordinates ) | |
| float x = ( float ) e.getAxisCoordinatesX( ); | |
| float y = ( float ) e.getAxisCoordinatesY( ); | |
| float xMin = x - MAX_POLY_BOUND_BOX / 2.0f; | |
| float xMax = x + MAX_POLY_BOUND_BOX / 2.0f; | |
| float yMin = y - MAX_POLY_BOUND_BOX / 2.0f; | |
| float yMax = y + MAX_POLY_BOUND_BOX / 2.0f; | |
| Collection<Point> results = quadTree.search( xMin, xMax, yMin, yMax ); | |
| // we've narrowed down the possible polygons inside the mouse click position | |
| // using the quadTree (for efficiency), now perform an interior check on each candidate | |
| for ( Point point : results ) | |
| { | |
| Polygon polygon = polygonMap.get( point.getId( ) ); | |
| // choose the first polygon we find (even if there might be multiples | |
| if ( polygon.contains( x, y ) ) | |
| { | |
| initialSelectedPolygon = polygon; | |
| initialX = x; | |
| initialY = y; | |
| break; | |
| } | |
| } | |
| // if an icon is selected, lock the axes so that the icon can | |
| // be dragged around without translating the plot | |
| if ( initialSelectedPolygon != null ) | |
| { | |
| e.setHandled( true ); | |
| } | |
| } | |
| @Override | |
| public void mouseReleased( GlimpseMouseEvent event ) | |
| { | |
| // updated the quadtree with the result of the drag | |
| if ( initialSelectedPolygon != null && currentSelectedPolygon != null ) | |
| { | |
| quadTree.remove( initialSelectedPolygon.getCenterPoint( ) ); | |
| quadTree.add( currentSelectedPolygon.getCenterPoint( ) ); | |
| } | |
| currentSelectedPolygon = null; | |
| initialSelectedPolygon = null; | |
| } | |
| } ); | |
| return layout; | |
| } | |
| public void addPolygon( float[] xpoints, float[] ypoints ) | |
| { | |
| int groupId = id++; | |
| painter.clearGroup( groupId ); | |
| polygonMap.remove( groupId ); | |
| Polygon polygon = new Polygon( groupId, xpoints, ypoints ); | |
| Rectangle2D bounds = polygon.getPath( ).getBounds2D( ); | |
| // if larger polygons are added, we must search using | |
| // a larger bounding box in the quadtree (because we currently | |
| // only store polygon centers in the quadtree) | |
| float maxSideSize = ( float ) Math.max( bounds.getHeight( ), bounds.getWidth( ) ); | |
| if ( maxSideSize > MAX_POLY_BOUND_BOX ) | |
| { | |
| MAX_POLY_BOUND_BOX = maxSideSize; | |
| } | |
| // set polygon display characteristics | |
| painter.setFill( groupId, true ); | |
| painter.setFillColor( groupId, GlimpseColor.getBlack( 0.7f ) ); | |
| painter.setLineWidth( groupId, 2.0f ); | |
| painter.setLineColor( groupId, GlimpseColor.getBlack( 1.0f ) ); | |
| // add polygon to painter so it is drawn on screen | |
| painter.addPolygon( groupId, 0, xpoints, ypoints, 0.0f ); | |
| // add Polygon to Map indexed by groupId so that we can query | |
| // polygons for whether the mouse click point is interior to them | |
| polygonMap.put( groupId, polygon ); | |
| // add the center of each polygon to a QuadTree so that we can rule | |
| // out polygons which are far from the mouse click point without | |
| // having to call Path2D.Float.contains( ) on each polygon | |
| quadTree.add( polygon.getCenterPoint( ) ); | |
| } | |
| public static class Polygon | |
| { | |
| private int id; | |
| private Path2D.Float path; | |
| private Point centerPoint; | |
| private float[] verticesX; | |
| private float[] verticesY; | |
| public Polygon( int id, Float path, Point centerPoint, float[] verticesX, float[] verticesY ) | |
| { | |
| this.id = id; | |
| this.path = path; | |
| this.centerPoint = centerPoint; | |
| this.verticesX = verticesX; | |
| this.verticesY = verticesY; | |
| } | |
| public Polygon( int id, float[] verticesX, float[] verticesY ) | |
| { | |
| int size = verticesX.length; | |
| Float path = new Path2D.Float( PathIterator.WIND_NON_ZERO, size ); | |
| float centerX = verticesX[0]; | |
| float centerY = verticesY[0]; | |
| path.moveTo( verticesX[0], verticesY[0] ); | |
| for ( int i = 1; i < size; i++ ) | |
| { | |
| centerX += verticesX[i]; | |
| centerY += verticesY[i]; | |
| path.lineTo( verticesX[i], verticesY[i] ); | |
| } | |
| centerX /= size; | |
| centerY /= size; | |
| path.closePath( ); | |
| this.id = id; | |
| this.path = path; | |
| this.centerPoint = new Point( id, id, centerX, centerY, 0 ); | |
| this.verticesX = verticesX; | |
| this.verticesY = verticesY; | |
| } | |
| public Polygon newTranslatedPolygon( float dx, float dy ) | |
| { | |
| int size = getSize( ); | |
| float[] newVerticesX = new float[size]; | |
| float[] newVerticesY = new float[size]; | |
| for ( int i = 0; i < size; i++ ) | |
| { | |
| newVerticesX[i] = verticesX[i] + dx; | |
| newVerticesY[i] = verticesY[i] + dy; | |
| } | |
| return new Polygon( getId( ), newVerticesX, newVerticesY ); | |
| } | |
| public int getId( ) | |
| { | |
| return id; | |
| } | |
| public int getSize( ) | |
| { | |
| return this.verticesX.length; | |
| } | |
| public Point getCenterPoint( ) | |
| { | |
| return centerPoint; | |
| } | |
| public Path2D.Float getPath( ) | |
| { | |
| return path; | |
| } | |
| public float[] getVerticesX( ) | |
| { | |
| return verticesX; | |
| } | |
| public float[] getVerticesY( ) | |
| { | |
| return verticesY; | |
| } | |
| public boolean contains( double x, double y ) | |
| { | |
| return path.contains( x, y ); | |
| } | |
| } | |
| } | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment