Skip to content

Commit

Permalink
ttools: add option to anchor plane X/Y axes at data value of zero
Browse files Browse the repository at this point in the history
This is specially useful for histogram, since it means you can fix
the X axis to sit at the bottom of the plot.
Realised while doing this that the configuration parameters for
navigators really belong with the SurfaceFactory not with the Navigator
itself.  Rectified this for PlaneSurfaceFactory (and histogram),
but not for Time, Sky, Cube etc.
  • Loading branch information
mbtaylor authored and mmpcn committed Nov 27, 2014
1 parent b1d8c92 commit 758f3de
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 47 deletions.
Expand Up @@ -3,11 +3,14 @@
import java.util.Arrays;
import uk.ac.starlink.topcat.ResourceIcon;
import uk.ac.starlink.ttools.plot.Style;
import uk.ac.starlink.ttools.plot2.Navigator;
import uk.ac.starlink.ttools.plot2.PlotLayer;
import uk.ac.starlink.ttools.plot2.SurfaceFactory;
import uk.ac.starlink.ttools.plot2.config.ConfigKey;
import uk.ac.starlink.ttools.plot2.config.ConfigMap;
import uk.ac.starlink.ttools.plot2.config.StyleKeys;
import uk.ac.starlink.ttools.plot2.geom.PlaneAspect;
import uk.ac.starlink.ttools.plot2.geom.PlaneNavigator;
import uk.ac.starlink.ttools.plot2.geom.PlaneSurfaceFactory;
import uk.ac.starlink.ttools.plot2.layer.BinSizer;
import uk.ac.starlink.ttools.plot2.layer.HistogramPlotter;
Expand Down Expand Up @@ -36,7 +39,7 @@ public class HistogramAxisController
* @param stack control stack
*/
public HistogramAxisController( ControlStack stack ) {
super( new PlaneSurfaceFactory(),
super( new HistogramSurfaceFactory(),
PlaneAxisController.createAxisLabelKeys(), stack );
SurfaceFactory surfFact = getSurfaceFactory();
ConfigControl mainControl = getMainControl();
Expand Down Expand Up @@ -164,6 +167,41 @@ private static BarState getBarState( PlotLayer[] layers ) {
return null;
}

/**
* Surface factory for histogram.
*/
private static class HistogramSurfaceFactory extends PlaneSurfaceFactory {

private static final ConfigKey<Boolean> HIST_XANCHOR0_KEY =
createAnchor0Key( "X", false );
private static final ConfigKey<Boolean> HIST_YANCHOR0_KEY =
createAnchor0Key( "Y", true );

@Override
public ConfigKey[] getNavigatorKeys() {
return new ConfigKey[] {
NAVAXES_KEY,
HIST_XANCHOR0_KEY,
HIST_YANCHOR0_KEY,
StyleKeys.ZOOM_FACTOR,
};
}

@Override
public Navigator<PlaneAspect> createNavigator( ConfigMap navConfig ) {
double zoom = navConfig.get( StyleKeys.ZOOM_FACTOR );
boolean[] navFlags = navConfig.get( NAVAXES_KEY );
boolean xnav = navFlags[ 0 ];
boolean ynav = navFlags[ 1 ];
double xAnchor = navConfig.get( HIST_XANCHOR0_KEY ) ? 0.0
: Double.NaN;
double yAnchor = navConfig.get( HIST_YANCHOR0_KEY ) ? 0.0
: Double.NaN;
return new PlaneNavigator( zoom, xnav, ynav, xnav, ynav,
xAnchor, yAnchor );
}
}

/**
* Characterises how histogram bars are laid out on the plot surface.
*/
Expand Down
Expand Up @@ -12,11 +12,6 @@
import uk.ac.starlink.ttools.plot2.Navigator;
import uk.ac.starlink.ttools.plot2.PlotUtil;
import uk.ac.starlink.ttools.plot2.Surface;
import uk.ac.starlink.ttools.plot2.config.CombinationConfigKey;
import uk.ac.starlink.ttools.plot2.config.ConfigKey;
import uk.ac.starlink.ttools.plot2.config.ConfigMap;
import uk.ac.starlink.ttools.plot2.config.ConfigMeta;
import uk.ac.starlink.ttools.plot2.config.StyleKeys;

/**
* Navigator for use with plane plot.
Expand All @@ -31,11 +26,8 @@ public class PlaneNavigator implements Navigator<PlaneAspect> {
private final boolean yZoom_;
private final boolean xPan_;
private final boolean yPan_;

/** Config key to select which axes zoom will operate on. */
public static final ConfigKey<boolean[]> NAVAXES_KEY =
new CombinationConfigKey( new ConfigMeta( "navaxes", "Pan/Zoom Axes" ),
new String[] { "X", "Y" } );
private final double xAnchor_;
private final double yAnchor_;

/**
* Constructor.
Expand All @@ -45,14 +37,21 @@ public class PlaneNavigator implements Navigator<PlaneAspect> {
* @param yZoom true iff wheel operation will zoom in Y direction
* @param xPan true iff drag operation will pan in X direction
* @param yPan true iff drag operation will pan in Y direction
* @param xAnchor data value to pin X coordinate at during zooms;
* NaN for no anchor
* @param yAnchor data value to pin Y coordinate at during zooms;
* NaN for no anchor
*/
public PlaneNavigator( double zoomFactor, boolean xZoom, boolean yZoom,
boolean xPan, boolean yPan ) {
boolean xPan, boolean yPan,
double xAnchor, double yAnchor ) {
zoomFactor_ = zoomFactor;
xZoom_ = xZoom;
yZoom_ = yZoom;
xPan_ = xPan;
yPan_ = yPan;
xAnchor_ = xAnchor;
yAnchor_ = yAnchor;
}

public NavAction<PlaneAspect> drag( Surface surface, MouseEvent evt,
Expand All @@ -61,16 +60,19 @@ public NavAction<PlaneAspect> drag( Surface surface, MouseEvent evt,
PlaneSurface psurf = (PlaneSurface) surface;
Point point = evt.getPoint();
if ( PlotUtil.isZoomDrag( evt ) ) {
int[] offs = getAnchorOffsets( psurf, origin );
Point g0 = new Point( origin.x + offs[ 0 ], origin.y + offs[ 1 ] );
Point gp = new Point( point.x + offs[ 0 ], point.y + offs[ 1 ] );
double xf = useFlags[ 0 ]
? PlotUtil.toZoom( zoomFactor_, origin, point, false )
? PlotUtil.toZoom( zoomFactor_, g0, gp, false )
: 1;
double yf = useFlags[ 1 ]
? PlotUtil.toZoom( zoomFactor_, origin, point, true )
? PlotUtil.toZoom( zoomFactor_, g0, gp, true )
: 1;
PlaneAspect aspect = psurf.zoom( origin, xf, yf );
PlaneAspect aspect = psurf.zoom( g0, xf, yf );
Decoration dec =
NavDecorations
.createDragDecoration( origin, xf, yf,
.createDragDecoration( g0, xf, yf,
useFlags[ 0 ], useFlags[ 1 ],
surface.getPlotBounds() );
return new NavAction<PlaneAspect>( aspect, dec );
Expand All @@ -84,15 +86,18 @@ public NavAction<PlaneAspect> drag( Surface surface, MouseEvent evt,

public NavAction<PlaneAspect> wheel( Surface surface,
MouseWheelEvent evt ) {
PlaneSurface psurf = (PlaneSurface) surface;
Point pos = evt.getPoint();
boolean[] useFlags = getAxisNavFlags( surface, pos, xZoom_, yZoom_ );
double zfact = PlotUtil.toZoom( zoomFactor_, evt );
double xf = useFlags[ 0 ] ? zfact : 1;
double yf = useFlags[ 1 ] ? zfact : 1;
PlaneAspect aspect = ((PlaneSurface) surface).zoom( pos, xf, yf );
int[] offs = getAnchorOffsets( psurf, pos );
Point gp = new Point( pos.x + offs[ 0 ], pos.y + offs[ 1 ] );
PlaneAspect aspect = psurf.zoom( gp, xf, yf );
Decoration dec =
NavDecorations
.createWheelDecoration( pos, xf, yf, useFlags[ 0 ], useFlags[ 1 ],
.createWheelDecoration( gp, xf, yf, useFlags[ 0 ], useFlags[ 1 ],
surface.getPlotBounds() );
return new NavAction<PlaneAspect>( aspect, dec );
}
Expand Down Expand Up @@ -131,6 +136,37 @@ else if ( yUse ) {
return map;
}

/**
* Calculates offsets to a reference point required to achieve
* anchoring of zoom operations at the X/Y anchor values set for this
* navigator. If no anchor values are set, the offsets will be zero.
*
* @param surface current surface
* @param refpos reference graphics position on submitted surface
* @return 2-element array giving X,Y graphics coordinate offsets to
* refpos for anchoring
*/
private int[] getAnchorOffsets( PlaneSurface surface, Point refpos ) {
double[] d0 = surface
.graphicsToData( surface.getPlotBounds().getLocation(),
null );
Point pc = new Point();
boolean[] logFlags = surface.getLogFlags();
int xoff = ! Double.isNaN( xAnchor_ ) &&
( xAnchor_ > 0 || ! logFlags[ 0 ] ) &&
surface.dataToGraphics( new double[] { xAnchor_, d0[ 1 ] },
false, pc )
? pc.x - refpos.x
: 0;
int yoff = ! Double.isNaN( yAnchor_ ) &&
( yAnchor_ > 0 || ! logFlags[ 1 ] ) &&
surface.dataToGraphics( new double[] { d0[ 0 ], yAnchor_ },
false, pc )
? pc.y - refpos.y
: 0;
return new int[] { xoff, yoff };
}

/**
* Determines which axes navigation should be performed on.
* Navigation may be active by default on zero or more axes,
Expand Down Expand Up @@ -163,31 +199,4 @@ else if ( inY && ! inX ) {
return new boolean[] { false, false };
}
}

/**
* Returns the config keys for use with this navigator.
*
* @return config keys
*/
public static ConfigKey[] getConfigKeys() {
return new ConfigKey[] {
NAVAXES_KEY,
StyleKeys.ZOOM_FACTOR,
};
}

/**
* Creates a navigator instance from a config map.
* The keys defined by {@link #getConfigKeys} are used.
*
* @param navConfig configuration map
* @return navigator
*/
public static PlaneNavigator createNavigator( ConfigMap navConfig ) {
boolean[] navFlags = navConfig.get( NAVAXES_KEY );
boolean xnav = navFlags[ 0 ];
boolean ynav = navFlags[ 1 ];
double zoom = navConfig.get( StyleKeys.ZOOM_FACTOR );
return new PlaneNavigator( zoom, xnav, ynav, xnav, ynav );
}
}
Expand Up @@ -14,6 +14,7 @@
import uk.ac.starlink.ttools.plot2.Surface;
import uk.ac.starlink.ttools.plot2.SurfaceFactory;
import uk.ac.starlink.ttools.plot2.config.BooleanConfigKey;
import uk.ac.starlink.ttools.plot2.config.CombinationConfigKey;
import uk.ac.starlink.ttools.plot2.config.ConfigException;
import uk.ac.starlink.ttools.plot2.config.ConfigKey;
import uk.ac.starlink.ttools.plot2.config.ConfigMap;
Expand Down Expand Up @@ -100,6 +101,19 @@ public class PlaneSurfaceFactory
StyleKeys.createCrowdKey( new ConfigMeta( "ycrowd",
"Y Tick Crowding" ) );

/** Config key to select which axes navigation actions will operate on. */
public static final ConfigKey<boolean[]> NAVAXES_KEY =
new CombinationConfigKey( new ConfigMeta( "navaxes", "Pan/Zoom Axes" ),
new String[] { "X", "Y" } );

/** Config key to anchor X axis during zooms. */
public static final ConfigKey<Boolean> XANCHOR0_KEY =
createAnchor0Key( "X", false );

/** Config key to anchor Y axis during zooms. */
public static final ConfigKey<Boolean> YANCHOR0_KEY =
createAnchor0Key( "Y", false );

public Surface createSurface( Rectangle plotBounds, Profile profile,
PlaneAspect aspect ) {
Profile p = profile;
Expand Down Expand Up @@ -190,11 +204,40 @@ public Range[] readRanges( Profile profile, PlotLayer[] layers,
}

public ConfigKey[] getNavigatorKeys() {
return PlaneNavigator.getConfigKeys();
return new ConfigKey[] {
NAVAXES_KEY,
XANCHOR0_KEY,
YANCHOR0_KEY,
StyleKeys.ZOOM_FACTOR,
};
}

public Navigator<PlaneAspect> createNavigator( ConfigMap navConfig ) {
return PlaneNavigator.createNavigator( navConfig );
double zoom = navConfig.get( StyleKeys.ZOOM_FACTOR );
boolean[] navFlags = navConfig.get( NAVAXES_KEY );
boolean xnav = navFlags[ 0 ];
boolean ynav = navFlags[ 1 ];
double xAnchor = navConfig.get( XANCHOR0_KEY ) ? 0.0 : Double.NaN;
double yAnchor = navConfig.get( YANCHOR0_KEY ) ? 0.0 : Double.NaN;
return new PlaneNavigator( zoom, xnav, ynav, xnav, ynav,
xAnchor, yAnchor );
}

/**
* Creates a config key for determining whether one of the axes is
* to be anchored at a data value of zero.
*
* @param axname axis name
* @param dflt anchor default value
* @return config key
*/
public static ConfigKey<Boolean> createAnchor0Key( String axname,
boolean dflt ) {
String axl = axname.toLowerCase();
String axL = axname.toUpperCase();
ConfigMeta meta =
new ConfigMeta( axl + "anchor0", "Anchor " + axL + " axis" );
return new BooleanConfigKey( meta, dflt );
}

/**
Expand Down

0 comments on commit 758f3de

Please sign in to comment.