Skip to content

Instantly share code, notes, and snippets.

@ulmangt
Created September 13, 2012 02:41
Show Gist options
  • Save ulmangt/3711500 to your computer and use it in GitHub Desktop.
Save ulmangt/3711500 to your computer and use it in GitHub Desktop.
A Glimpse example demonstrating dragging PolygonPainter polygons
/*
* 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