Skip to content

Commit

Permalink
topcat: add framework for explicit plot geometry setting
Browse files Browse the repository at this point in the history
Add hooks to allow a PlotPosition object to be supplied from the GUI.
This can provide explicit settings for the size in pixels of the
generated plot graphic, as well as the borders between the exterior
of the graphic and the plot data region.  Any or all of these items
may be omitted, in which case defaults are calculated and used.

At this commit, no corresponding GUI has been implemented.
  • Loading branch information
mbtaylor committed Dec 19, 2014
1 parent 9b6b2cc commit a4cdf05
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 28 deletions.
162 changes: 135 additions & 27 deletions topcat/src/main/uk/ac/starlink/topcat/plot2/PlotPanel.java
Expand Up @@ -112,6 +112,7 @@ public class PlotPanel<P,A> extends JComponent implements ActionListener {
private final DataStoreFactory storeFact_;
private final AxisController<P,A> axisController_;
private final Factory<PlotLayer[]> layerFact_;
private final Factory<PlotPosition> posFact_;
private final Factory<Icon> legendFact_;
private final Factory<float[]> legendPosFact_;
private final ShaderControl shaderControl_;
Expand Down Expand Up @@ -159,6 +160,7 @@ public class PlotPanel<P,A> extends JComponent implements ActionListener {
* @param storeFact data store factory implementation
* @param axisController axis control GUI component
* @param layerFact supplier of plot layers
* @param posFact supplier of plot position settings
* @param legendFact supplier of legend icon
* @param legendPosFact supplier of legend position
* (2-element x,y fractional location in range 0-1,
Expand All @@ -174,8 +176,9 @@ public class PlotPanel<P,A> extends JComponent implements ActionListener {
*/
public PlotPanel( DataStoreFactory storeFact,
AxisController<P,A> axisController,
Factory<PlotLayer[]> layerFact, Factory<Icon> legendFact,
Factory<float[]> legendPosFact,
Factory<PlotLayer[]> layerFact,
Factory<PlotPosition> posFact,
Factory<Icon> legendFact, Factory<float[]> legendPosFact,
ShaderControl shaderControl,
ToggleButtonModel sketchModel,
PaperTypeSelector ptSel, Compositor compositor,
Expand All @@ -186,6 +189,7 @@ public PlotPanel( DataStoreFactory storeFact,
: new ProgressDataStoreFactory( storeFact, progModel );
axisController_ = axisController;
layerFact_ = layerFact;
posFact_ = posFact;
legendFact_ = legendFact;
legendPosFact_ = legendPosFact;
shaderControl_ = shaderControl;
Expand Down Expand Up @@ -515,7 +519,9 @@ private PlotJob<P,A> createPlotJob() {
Icon legend = legendFact_.getItem();
assert legend == null || legendFact_.getItem().equals( legend );
float[] legpos = legendPosFact_.getItem();
Rectangle bounds = getOuterBounds();
PlotPosition plotpos = posFact_.getItem();
Rectangle bounds = getOuterBounds( plotpos.getPlotSize() );
Insets insets = plotpos.getPlotInsets();
LayerOpt[] opts = PaperTypeSelector.getOpts( layers );
PaperType paperType =
ptSel_.getPixelPaperType( opts, compositor_, this );
Expand All @@ -538,8 +544,8 @@ private PlotJob<P,A> createPlotJob() {
fixAspect, geomFixRanges, surfConfig,
shadeFact, auxFixRanges, auxSubranges,
auxLogFlags, legend, legpos, storeFact_,
bounds, paperType, graphicsConfig, bgColor,
highlights );
bounds, insets, paperType, graphicsConfig,
bgColor, highlights );
}

/**
Expand All @@ -552,6 +558,18 @@ protected void paintComponent( Graphics g ) {
if ( plotIcon != null ) {
Insets insets = getInsets();
plotIcon.paintIcon( this, g, insets.left, insets.top );

/* Draw a border around the outside of the plot icon.
* This will normally be invisible, since the plot icon is sized
* to fit this component. However, if the size has been set
* explicitly (by supplying a PlotPosition object), it's useful
* to be able to see where the outline is. */
Color color0 = g.getColor();
g.setColor( Color.GRAY );
g.drawRect( insets.left - 1, insets.top - 1,
plotIcon.getIconWidth() + 1,
plotIcon.getIconHeight() + 1 );
g.setColor( color0 );
}
Decoration navdec = navDecoration_;
if ( navdec != null ) {
Expand All @@ -563,13 +581,25 @@ protected void paintComponent( Graphics g ) {
* Returns the bounds to use for the plot icon.
* This includes axis decorations etc, but excludes component insets.
*
* @param sizeSetting explicit settings for icon size, or null;
* negative members are ignored
* @return plot drawing bounds
*/
private Rectangle getOuterBounds() {
private Rectangle getOuterBounds( Dimension sizeSetting ) {
Insets insets = getInsets();
return new Rectangle( insets.left, insets.top,
getWidth() - insets.left - insets.right,
getHeight() - insets.top - insets.bottom );
int x = insets.left;
int y = insets.top;
int width = getWidth() - insets.left - insets.right;;
int height = getHeight() - insets.top - insets.bottom;
if ( sizeSetting != null ) {
if ( sizeSetting.width > 0 ) {
width = sizeSetting.width;
}
if ( sizeSetting.height > 0 ) {
height = sizeSetting.height;
}
}
return new Rectangle( x, y, width, height );
}

/**
Expand Down Expand Up @@ -790,6 +820,7 @@ private static class PlotJob<P,A> {
private final float[] legpos_;
private final DataStoreFactory storeFact_;
private final Rectangle bounds_;
private final Insets insets_;
private final PaperType paperType_;
private final GraphicsConfiguration graphicsConfig_;
private final Color bgColor_;
Expand Down Expand Up @@ -817,6 +848,8 @@ private static class PlotJob<P,A> {
* positions (0-1), or null if legend absent/external
* @param storeFact data store factory implementation
* @param bounds plot data bounds
* @param insets space reserved for annotations between
* the plot data bounds and external bounds
* @param paperType rendering implementation
* @param graphicsConfig graphics configuration
* @param bgColor background colour
Expand All @@ -830,7 +863,7 @@ private static class PlotJob<P,A> {
Map<AuxScale,Subrange> auxSubranges,
Map<AuxScale,Boolean> auxLogFlags,
Icon legend, float[] legpos, DataStoreFactory storeFact,
Rectangle bounds, PaperType paperType,
Rectangle bounds, Insets insets, PaperType paperType,
GraphicsConfiguration graphicsConfig, Color bgColor,
double[][] highlights ) {
oldWorkings_ = oldWorkings;
Expand All @@ -848,6 +881,7 @@ private static class PlotJob<P,A> {
legpos_ = legpos;
storeFact_ = storeFact;
bounds_ = bounds;
insets_ = insets;
paperType_ = paperType;
graphicsConfig_ = graphicsConfig;
bgColor_ = bgColor;
Expand Down Expand Up @@ -1107,38 +1141,55 @@ else if ( ! surfFact_.useRanges( profile_, aspectConfig_ ) ) {
Range shadeRange = auxClipRanges.get( AuxScale.COLOR );
ShadeAxis shadeAxis = shadeFact_.createShadeAxis( shadeRange );

/* Work out the plot placement and plot surface. */
PlotPlacement placer =
PlotPlacement.createPlacement( bounds_, surfFact_, profile_,
aspect, true, legend_,
legpos_, shadeAxis );
assert PlotPlacement
.createPlacement( bounds_, surfFact_, profile_,
aspect, true, legend_,
legpos_, shadeAxis )
.equals( placer );
Surface surface = placer.getSurface();

/* Place highlighted point icons as plot decorations. */
/* Work out the graphics bounds of the data region. */
final Rectangle dataBounds;
if ( isFixedInsets( insets_ ) ) {
dataBounds = PlotUtil.subtractInsets( bounds_, insets_ );
}
else {
Rectangle autoDataBounds =
PlotPlacement
.calculateDataBounds( bounds_, surfFact_, profile_, aspect,
true, legend_, legpos_, shadeAxis );
dataBounds = adjustDataBounds( bounds_, autoDataBounds,
insets_ );
}

/* Get the plot surface. */
Surface surface =
surfFact_.createSurface( dataBounds, profile_, aspect );

/* Get the basic plot decorations. */
Decoration[] basicDecs =
PlotPlacement.createPlotDecorations( dataBounds, legend_,
legpos_, shadeAxis );
List<Decoration> decList = new ArrayList<Decoration>();
decList.addAll( Arrays.asList( basicDecs ) );

/* Place highlighted point icons as further plot decorations. */
Icon highIcon = HIGHLIGHTER;
int xoff = highIcon.getIconWidth() / 2;
int yoff = highIcon.getIconHeight() / 2;
Point gp = new Point();
for ( int ih = 0; ih < highlights_.length; ih++ ) {
if ( surface.dataToGraphics( highlights_[ ih ], true, gp ) ) {
placer.getDecorations()
.add( new Decoration( highIcon,
gp.x - xoff, gp.y - yoff ) );
decList.add( new Decoration( highIcon,
gp.x - xoff, gp.y - yoff ) );
}
}

/* Construct the plot placement. */
Decoration[] decs = decList.toArray( new Decoration[ 0 ] );
PlotPlacement placer = new PlotPlacement( bounds_, surface, decs );
assert placer.equals( new PlotPlacement( bounds_, surface, decs ) );

/* Determine whether first the data part, then the entire
* graphics, of the plot is the same as for the oldWorkings.
* If so, it's likely that we've got this far without any
* expensive calculations (data scans), since the ranges
* will have been picked up from the previous plot. */
boolean sameDataIcon =
new DataIconId( placer.getSurface(), layers, auxClipRanges )
new DataIconId( surface, layers, auxClipRanges )
.equals( oldWorkings_.getDataIconId() );
boolean samePlot =
sameDataIcon &&
Expand Down Expand Up @@ -1288,6 +1339,63 @@ private static boolean hasData( DataStore dstore, DataSpec[] dspecs ) {
}
}

/**
* Determines whether an Insets object contains full inset information.
*
* @param insets represents explicit inset settings
* @return true iff insets is not null and all its members are &gt;=0
*/
private static boolean isFixedInsets( Insets insets ) {
return insets != null
&& insets.top >= 0
&& insets.left >= 0
&& insets.bottom >= 0
&& insets.right >= 0;
}

/**
* Returns a data bounds rectangle based on given external plot bounds,
* automatically calculated data bounds, and an optional insets
* object providing preferred settings. The values in the insets
* object are used to override those from the autoBounds input
* where present.
*
* @param extBounds fixed external bounds
* @param autoBounds default data bounds
* @param insets may contain required insets between external and
* data bounds; members may be negative to indicate
* no setting, or the whole thing can be null
* @return data bounds rectangle for actual use
*/
private static Rectangle adjustDataBounds( Rectangle extBounds,
Rectangle autoBounds,
Insets insets ) {
if ( insets == null ) {
return new Rectangle( autoBounds );
}
int top =
insets.top >= 0
? insets.top
: autoBounds.y - extBounds.y;
int left =
insets.left >= 0
? insets.left
: autoBounds.x - extBounds.x;
int bottom =
insets.bottom >= 0
? insets.bottom
: extBounds.y + extBounds.height
- autoBounds.y - autoBounds.height;
int right =
insets.right >= 0
? insets.right
: extBounds.x + extBounds.width
- autoBounds.x - autoBounds.width;
return PlotUtil
.subtractInsets( extBounds,
new Insets( top, left, bottom, right ) );
}

/**
* Calculates plot plans for a set of drawings, attempting to re-use
* previously calculated plans where possible.
Expand Down
94 changes: 94 additions & 0 deletions topcat/src/main/uk/ac/starlink/topcat/plot2/PlotPosition.java
@@ -0,0 +1,94 @@
package uk.ac.starlink.topcat.plot2;

import java.awt.Dimension;
import java.awt.Insets;
import uk.ac.starlink.ttools.plot2.Equality;
import uk.ac.starlink.ttools.plot2.PlotUtil;

/**
* Characterises explicit settings for how to position a plot component
* in graphics coordinates.
* This aggregates a Dimension object, giving the external dimensions
* of the whole plot graphic, and an Insets object, giving the gaps
* between the dimension and the data region of the plot (this is where
* axis annotations etc are usually drawn).
*
* <p>Either or both of these items may be null, indicating that no
* explicit settings are in force. Additionally any of the integer members
* of the size or insets may be negative, meaning that there is no explicit
* setting for the value in question. Where there is no explicit setting,
* the plotting machinery is expected to come up with sensible defaults.
*
* <p>Although Dimension and Insets are mutable, they are treated here
* as if immutable (cloned on the way in and out).
*
* @author Mark Taylor
* @since 18 Dec 2014
*/
@Equality
public class PlotPosition {

private final Dimension size_;
private final Insets insets_;

/**
* Constructs a PlotPosition with no explicit settings.
*/
public PlotPosition() {
this( null, null );
}

/**
* Constructs a PlotPosition from a Dimension and Insets.
*
* @param size external plot dimensions, may be null
* @param insets border between external plot dimensions and data region
*/
public PlotPosition( Dimension size, Insets insets ) {
size_ = size == null ? null : size.getSize();
insets_ = insets == null ? null : (Insets) insets.clone();
}

/**
* Returns settings for the exterior dimensions of a plot.
* The return value may be null, or either of its members may be -1,
* indicating no default setting.
*
* @return settings for plot exterior size
*/
public Dimension getPlotSize() {
return size_ == null ? null : size_.getSize();
}

/**
* Returns settings for the border between the data region and exterior
* dimensions of a plot.
* The return value may be null, or any of its members may be -1,
* indicating no default setting.
*
* @return settings for border between plot data region and exterior
*/
public Insets getPlotInsets() {
return insets_ == null ? null : (Insets) insets_.clone();
}

@Override
public int hashCode() {
int code = 8874;
code = 23 * code + PlotUtil.hashCode( size_ );
code = 23 * code + PlotUtil.hashCode( insets_ );
return code;
}

@Override
public boolean equals( Object o ) {
if ( o instanceof PlotPosition ) {
PlotPosition other = (PlotPosition) o;
return PlotUtil.equals( this.size_, other.size_ )
&& PlotUtil.equals( this.insets_, other.insets_ );
}
else {
return false;
}
}
}
Expand Up @@ -158,6 +158,12 @@ public PlotLayer[] getItem() {
return readPlotLayers( true );
}
};
Factory<PlotPosition> posFact = new Factory<PlotPosition>() {
private final PlotPosition pos = new PlotPosition();
public PlotPosition getItem() {
return pos;
}
};
final LegendControl legendControl =
new LegendControl( stackModel_, configger );
Factory<Icon> legendFact = new Factory<Icon>() {
Expand Down Expand Up @@ -191,7 +197,7 @@ public float[] getItem() {
* requirements from the GUI. This does the actual plotting. */
plotPanel_ =
new PlotPanel<P,A>( storeFact, axisController_, layerFact,
legendFact, legendPosFact,
posFact, legendFact, legendPosFact,
shaderControl, sketchModel,
plotType.getPaperTypeSelector(), compositor,
placeProgressBar().getModel(),
Expand Down

0 comments on commit a4cdf05

Please sign in to comment.