Skip to content

Commit

Permalink
ttools: add anisotropic zoom navigation options.
Browse files Browse the repository at this point in the history
You can now zoom in a selection of dimensions (e.g. X only in an XY plot)
by specifying appropriate config options.  This is in place for the
STILTS plotting (Plot2Task) but does not yet have an associated GUI
in topcat.
  • Loading branch information
mbtaylor committed Oct 5, 2013
1 parent 797af7a commit cea7cbb
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 43 deletions.
@@ -0,0 +1,188 @@
package uk.ac.starlink.ttools.plot2.config;

import java.util.Arrays;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JComponent;

/**
* Config key that can select zero or more items from a short fixed list.
* The string representation is an unordered list of the first letters
* (lowercased) of each of the given option labels.
* So the labels had better have different initial letters.
*
* <p>Typically this is used for axes.
*
* @author Mark Taylor
* @since 5 Oct 2013
*/
public class CombinationConfigKey extends ConfigKey<boolean[]> {

private final int nopt_;
private final String[] optNames_;

/**
* Constructs an instance with a specified default.
*
* @param meta metadata
* @param dflt default array of selection flags
* @param optNames labels for each of the options that may be selected,
* same length as <code>dflt</code>
*/
public CombinationConfigKey( ConfigMeta meta, boolean[] dflt,
String[] optNames ) {
super( meta, boolean[].class, dflt );
optNames_ = optNames;
nopt_ = optNames.length;
if ( dflt.length != nopt_ ) {
throw new IllegalArgumentException( "Array length mismatch" );
}
}

/**
* Constructs an instance where all the default inclusion flags are true.
*
* @param meta metadata
* @param optNames labels for each of the options that may be selected,
*/
public CombinationConfigKey( ConfigMeta meta, String[] optNames ) {
this( meta, createTrueArray( optNames.length ), optNames );
}

public boolean[] stringToValue( String txt ) throws ConfigException {
boolean[] value = new boolean[ nopt_ ];
for ( int ic = 0; ic < txt.length(); ic++ ) {
value[ optCharToIndex( txt.charAt( ic ) ) ] = true;
}
return value;
}

public String valueToString( boolean[] opts ) {
StringBuffer sbuf = new StringBuffer();
for ( int io = 0; io < nopt_; io++ ) {
if ( opts[ io ] ) {
sbuf.append( optIndexToChar( io ) );
}
}
return sbuf.toString();
}

public Specifier<boolean[]> createSpecifier() {
return new CheckBoxesSpecifier();
}

/**
* Gets the option index from an initial character.
*
* @param c label character, case unimportant
* @return option index
* @throws ConfigException if no index is indicated (unknown letter)
*/
public int optCharToIndex( char c ) throws ConfigException {
char lc = Character.toLowerCase( c );
for ( int io = 0; io < nopt_; io++ ) {
if ( lc == optIndexToChar( io ) ) {
return io;
}
}
StringBuffer sbuf = new StringBuffer()
.append( "Unknown option letter '" )
.append( c )
.append( "'; expecting one of " );;
for ( int io = 0; io < nopt_; io++ ) {
if ( io > 0 ) {
sbuf.append( ", " );
}
sbuf.append( "'" )
.append( optIndexToChar( io ) )
.append( "'" );
}
throw new ConfigException( this, sbuf.toString() );
}

/**
* Gets the initial letter from the option index.
*
* @param io option index
* @return lowercased initial letter
*/
public char optIndexToChar( int io ) {
return Character.toLowerCase( optNames_[ io ].charAt( 0 ) );
}

/**
* Utility method to return a fixed-length boolean array with all
* true elements.
*
* @param n element count
* @return array of <code>n</code> true elements
*/
private static boolean[] createTrueArray( int n ) {
boolean[] a = new boolean[ n ];
Arrays.fill( a, true );
return a;
}

/**
* Specifier implementation for this key.
* It uses a row of check boxes, so there had better not be too many
* options.
*/
private class CheckBoxesSpecifier extends SpecifierPanel<boolean[]> {

private final JCheckBox[] checkBoxes_;

/**
* Constructor.
*/
CheckBoxesSpecifier() {
super( false );
checkBoxes_ = new JCheckBox[ nopt_ ];
for ( int io = 0; io < nopt_; io++ ) {
JCheckBox checkBox =
new JCheckBox( optNames_[ io ], getDefaultValue()[ io ] );
checkBox.addActionListener( getActionForwarder() );
}
}

public JComponent createComponent() {
JComponent box = new Box( BoxLayout.X_AXIS ) {
@Override
public void setEnabled( boolean enabled ) {
super.setEnabled( enabled );
for ( int io = 0; io < nopt_; io++ ) {
checkBoxes_[ io ].setEnabled( enabled );
}
}
};
for ( int io = 0; io < nopt_; io++ ) {
if ( io > 0 ) {
box.add( Box.createHorizontalStrut( 10 ) );
}
box.add( checkBoxes_[ io ] );
}
return box;
}

public boolean[] getSpecifiedValue() {
boolean[] flags = new boolean[ nopt_ ];
for ( int io = 0; io < nopt_; io++ ) {
flags[ io ] = checkBoxes_[ io ].isSelected();
}
return flags;
}

public void setSpecifiedValue( boolean[] flags ) {
boolean change = false;
for ( int io = 0; io < nopt_; io++ ) {
JCheckBox checkBox = checkBoxes_[ io ];
change = change || ( flags[ io ] ^ checkBox.isSelected() );
checkBox.setSelected( flags[ io ] );
}
if ( change ) {
fireAction();
}
}
}
}
Expand Up @@ -3,11 +3,15 @@
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.util.ArrayList;
import java.util.List;
import uk.ac.starlink.ttools.plot2.PlotUtil;
import uk.ac.starlink.ttools.plot2.Navigator;
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;

/**
Expand All @@ -19,14 +23,29 @@
public class CubeNavigator implements Navigator<CubeAspect> {

private final double zoomFactor_;
private final boolean xZoom_;
private final boolean yZoom_;
private final boolean zZoom_;

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

/**
* Constructor.
*
* @param zoomFactor amount of zoom for one mouse wheel click
* @param xZoom true iff wheel operation will zoom in X direction
* @param yZoom true iff wheel operation will zoom in Y direction
* @param zZoom true iff wheel operation will zoom in Z direction
*/
public CubeNavigator( double zoomFactor ) {
public CubeNavigator( double zoomFactor,
boolean xZoom, boolean yZoom, boolean zZoom ) {
zoomFactor_ = zoomFactor;
xZoom_ = xZoom;
yZoom_ = yZoom;
zZoom_ = zZoom;
}

public CubeAspect drag( Surface surface, MouseEvent evt, Point origin ) {
Expand All @@ -35,7 +54,8 @@ public CubeAspect drag( Surface surface, MouseEvent evt, Point origin ) {

public CubeAspect wheel( Surface surface, MouseWheelEvent evt ) {
return ((CubeSurface) surface)
.zoom( PlotUtil.toZoom( zoomFactor_, evt ) );
.zoom( PlotUtil.toZoom( zoomFactor_, evt ),
xZoom_, yZoom_, zZoom_ );
}

public CubeAspect click( Surface surface, MouseEvent evt,
Expand All @@ -47,23 +67,32 @@ public CubeAspect click( Surface surface, MouseEvent evt,
/**
* Returns the config keys for use with this navigator.
*
* @param isIso whether navigator will be configured for isotropic mode
* @return config keys
*/
public static ConfigKey[] getConfigKeys() {
return new ConfigKey[] {
StyleKeys.ZOOM_FACTOR,
};
public static ConfigKey[] getConfigKeys( boolean isIso ) {
List<ConfigKey> list = new ArrayList<ConfigKey>();
if ( ! isIso ) {
list.add( ZOOMAXES_KEY );
}
list.add( StyleKeys.ZOOM_FACTOR );
return list.toArray( new ConfigKey[ 0 ] );
}

/**
* Creates a navigator instance from a config map.
* The keys defined by {@link #getConfigKeys} are used.
*
* @param isIso whether navigator will be configured for isotropic mode
* @param config configuration map
* @return navigator
*/
public static CubeNavigator createNavigator( ConfigMap config ) {
public static CubeNavigator createNavigator( boolean isIso,
ConfigMap config ) {
double zoom = config.get( StyleKeys.ZOOM_FACTOR );
return new CubeNavigator( zoom );
boolean[] zoomFlags = isIso ? new boolean[] { true, true, true }
: config.get( ZOOMAXES_KEY );
return new CubeNavigator( zoom, zoomFlags[ 0 ], zoomFlags[ 1 ],
zoomFlags[ 2 ] );
}
}
34 changes: 25 additions & 9 deletions ttools/src/main/uk/ac/starlink/ttools/plot2/geom/CubeSurface.java
Expand Up @@ -410,19 +410,23 @@ CubeAspect pan( Point pos0, Point pos1 ) {

/**
* Returns a cube surface like this one but zoomed about its centre
* by a given factor.
* in some or all dimensions by a given factor.
*
* @param factor zoom factor
* @param xFlag true to zoom in X direction
* @param yFlag true to zoom in Y direction
* @param zFlag true to zoom in Z direction
* @return new cube
*/
CubeAspect zoom( double factor ) {
CubeAspect zoom( double factor,
boolean xFlag, boolean yFlag, boolean zFlag ) {
double[] midPos = new double[ 3 ];
for ( int i = 0; i < 3; i++ ) {
midPos[ i ] = logFlags_[ i ]
? Math.sqrt( dlos_[ i ] * dhis_[ i ] )
: ( dlos_[ i ] + dhis_[ i ] ) / 2.0;
}
return zoomData( factor, midPos );
return zoomData( factor, midPos, xFlag, yFlag, zFlag );
}

/**
Expand Down Expand Up @@ -464,19 +468,31 @@ CubeAspect center( double[] dpos ) {
*
* @param factor zoom factor
* @param dpos0 zoom centre in data coordinates
* @param xFlag true to zoom in X direction
* @param yFlag true to zoom in Y direction
* @param zFlag true to zoom in Z direction
* @return new cube
*/
CubeAspect zoomData( double factor, double[] dpos0 ) {
CubeAspect zoomData( double factor, double[] dpos0,
boolean xFlag, boolean yFlag, boolean zFlag ) {
boolean[] flags = new boolean[] { xFlag, yFlag, zFlag };
double[][] limits = new double[ 3 ][];
for ( int i = 0; i < 3; i++ ) {
double d0 = dpos0[ i ];
double dlo = dlos_[ i ];
double dhi = dhis_[ i ];
limits[ i ] = logFlags_[ i ]
? new double[] { d0 * Math.pow( dlo / d0, 1. / factor ),
d0 * Math.pow( dhi / d0, 1. / factor ) }
: new double[] { d0 + ( dlo - d0 ) / factor,
d0 + ( dhi - d0 ) / factor };
final double[] lims;
if ( flags[ i ] ) {
lims = logFlags_[ i ]
? new double[] { d0 * Math.pow( dlo / d0, 1. / factor ),
d0 * Math.pow( dhi / d0, 1. / factor ) }
: new double[] { d0 + ( dlo - d0 ) / factor,
d0 + ( dhi - d0 ) / factor };
}
else {
lims = new double[] { dlo, dhi };
}
limits[ i ] = lims;
}
return new CubeAspect( limits[ 0 ], limits[ 1 ], limits[ 2 ],
rotmat_, zoom_, xoff_, yoff_ );
Expand Down
Expand Up @@ -320,11 +320,11 @@ public Range[] readRanges( PlotLayer[] layers, DataStore dataStore ) {
}

public ConfigKey[] getNavigatorKeys() {
return CubeNavigator.getConfigKeys();
return CubeNavigator.getConfigKeys( isIso_ );
}

public Navigator<CubeAspect> createNavigator( ConfigMap navConfig ) {
return CubeNavigator.createNavigator( navConfig );
return CubeNavigator.createNavigator( isIso_, navConfig );
}

/**
Expand Down

0 comments on commit cea7cbb

Please sign in to comment.