001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030/** 031 * 032 */ 033package org.openimaj.vis.general; 034 035import java.util.ArrayList; 036import java.util.List; 037 038import org.openimaj.image.MBFImage; 039import org.openimaj.image.colour.RGBColour; 040import org.openimaj.vis.VisualisationImpl; 041import org.openimaj.vis.general.XYPlotVisualisation.LocatedObject; 042 043/** 044 * Abstract visualisation for plotting X,Y items. Uses the {@link AxesRenderer2D} 045 * to determine the scale of the visualisation. 046 * 047 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 048 * @param <O> The type of object to be visualised 049 * @created 3 Jun 2013 050 */ 051public class XYPlotVisualisation<O> extends VisualisationImpl<List<LocatedObject<O>>> 052{ 053 /** 054 * Class that locates an object. 055 * 056 * @param <O> The object type 057 * 058 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 059 * @created 3 Jun 2013 060 */ 061 public static class LocatedObject<O> 062 { 063 /** The x position */ 064 public double x; 065 066 /** The y position */ 067 public double y; 068 069 /** The object */ 070 public O object; 071 072 /** 073 * Create a located object 074 * 075 * @param x data point x location 076 * @param y data point y location 077 * @param object The object 078 */ 079 public LocatedObject( final double x, final double y, final O object ) 080 { 081 this.x = x; 082 this.y = y; 083 this.object = object; 084 } 085 } 086 087 /** */ 088 private static final long serialVersionUID = 1L; 089 090 /** The renderer for the axes */ 091 protected final AxesRenderer2D<Float[], MBFImage> axesRenderer2D = 092 new AxesRenderer2D<Float[], MBFImage>(); 093 094 /** Whether to render the axes on top of the data rather than underneath */ 095 private boolean renderAxesLast = false; 096 097 /** The item plotter to use */ 098 protected ItemPlotter<O, Float[], MBFImage> plotter; 099 100 /** Whether to scale the axes to fit the data */ 101 private boolean autoScaleAxes = true; 102 103 /** Whether to auto position the x axis - if false you'll have to do it yourself */ 104 private boolean autoPositionXAxis = true; 105 106 /** 107 * Default constructor 108 * 109 * @param plotter The item plotter to use 110 */ 111 public XYPlotVisualisation( final ItemPlotter<O, Float[], MBFImage> plotter ) 112 { 113 this.plotter = plotter; 114 this.init(); 115 } 116 117 /** 118 * Constructor that provides the width and height of the visualisation. 119 * 120 * @param width Width of the vis in pixels 121 * @param height Height of the vis in pixels 122 * @param plotter The item plotter to use 123 */ 124 public XYPlotVisualisation( final int width, final int height, final ItemPlotter<O, Float[], MBFImage> plotter ) 125 { 126 super( width, height ); 127 this.plotter = plotter; 128 this.init(); 129 } 130 131 /** 132 * Constructor that provides the width and height of the visualisation and a null plotter 133 * 134 * @param width Width of the vis in pixels 135 * @param height Height of the vis in pixels 136 */ 137 public XYPlotVisualisation( final int width, final int height ) 138 { 139 super( width, height ); 140 this.init(); 141 } 142 143 /** 144 * Initialise 145 */ 146 private void init() 147 { 148 this.data = new ArrayList<LocatedObject<O>>(); 149 150 // Set up a load of defaults for the axes renderer 151 this.axesRenderer2D.setxAxisColour( RGBColour.WHITE ); 152 this.axesRenderer2D.setyAxisColour( RGBColour.WHITE ); 153 this.axesRenderer2D.setMajorTickColour( RGBColour.WHITE ); 154 this.axesRenderer2D.setMinorTickColour( RGBColour.GRAY ); 155 this.axesRenderer2D.setxTickLabelColour( RGBColour.GRAY ); 156 this.axesRenderer2D.setyTickLabelColour( RGBColour.GRAY ); 157 this.axesRenderer2D.setxAxisNameColour( RGBColour.WHITE ); 158 this.axesRenderer2D.setyAxisNameColour( RGBColour.WHITE ); 159 this.axesRenderer2D.setMajorGridColour( new Float[]{.5f,.5f,.5f,1f} ); 160 this.axesRenderer2D.setMinorGridColour( new Float[]{.5f,.5f,.5f,1f} ); 161 this.axesRenderer2D.setDrawMajorTickGrid( true ); 162 this.axesRenderer2D.setDrawMinorTickGrid( true ); 163 } 164 165 /** 166 * {@inheritDoc} 167 * 168 * @see org.openimaj.vis.VisualisationImpl#update() 169 */ 170 @Override 171 public void update() 172 { 173 // Tell the axes renderer where we're drawing the axes to. 174 this.axesRenderer2D.setImage( this.visImage ); 175 176 // If we're going to auto position the axes we need to determine 177 // where in the display the x-axis will be positioned and set the 178 // axes renderer x axis position. 179 if( this.autoPositionXAxis ) 180 { 181 synchronized( this.axesRenderer2D ) 182 { 183 // Note, this might not work very well, if the axes are rotated. 184 final double xAxisPosition = this.axesRenderer2D.getAxisPaddingTop() + 185 this.axesRenderer2D.getyAxisConfig().getMaxValue() * 186 this.axesRenderer2D.getyAxisRenderer().getAxisLength() / 187 (this.axesRenderer2D.getyAxisConfig().getMaxValue() 188 - this.axesRenderer2D.getyAxisConfig().getMinValue()); 189 190 System.out.println( "Setting x position: "+xAxisPosition ); 191 192 this.axesRenderer2D.setxAxisPosition( xAxisPosition ); 193 } 194 } 195 196 // Recalculate any sizes needed for the axes 197 synchronized( this.axesRenderer2D ) 198 { 199 this.axesRenderer2D.precalc(); 200 } 201 202 // Call the beforeAxesRender callback so the vis can draw anything it need sto 203 this.beforeAxesRender( this.visImage, this.axesRenderer2D ); 204 205 // Render the axes if we're to do it below the plot 206 if( !this.renderAxesLast ) this.axesRenderer2D.renderAxis( this.visImage ); 207 208 // Tell the plotter we're about to start rendering items, 209 // then loop over the items plotting them 210 if( this.plotter != null ) 211 { 212 // Tell the plotter we're starting to render 213 this.plotter.renderRestarting(); 214 215 // Render each data point using the plotter 216 synchronized( this.data ) 217 { 218 for( final LocatedObject<O> o : this.data ) 219 this.plotter.plotObject( this.visImage, o, this.axesRenderer2D ); 220 } 221 } 222 223 // Render the axes if we're to do it on top of the plot 224 if( this.renderAxesLast ) this.axesRenderer2D.renderAxis( this.visImage ); 225 } 226 227 /** 228 * A method that can be overridden to plot something prior to the axes being 229 * drawn. 230 * 231 * @param visImage The image to draw to 232 * @param renderer The axes renderer 233 */ 234 public void beforeAxesRender( final MBFImage visImage, final AxesRenderer2D<Float[], MBFImage> renderer ) 235 { 236 // No implementation by default 237 } 238 239 /** 240 * Add an object to the plot 241 * 242 * @param x x location of data point 243 * @param y y location of data point 244 * @param object The object 245 */ 246 public void addPoint( final double x, final double y, final O object ) 247 { 248 this.data.add( new LocatedObject<O>( x, y, object ) ); 249 this.validateData(); 250 } 251 252 /** 253 * Remove a specific object 254 * 255 * @param object The object 256 */ 257 public void removePoint( final O object ) 258 { 259 LocatedObject<O> toRemove = null; 260 for( final LocatedObject<O> o : this.data ) 261 { 262 if( o.object.equals( object ) ) 263 { 264 toRemove = o; 265 break; 266 } 267 } 268 269 if( toRemove != null ) this.data.remove( toRemove ); 270 this.validateData(); 271 } 272 273 /** 274 * Set the plotter 275 * 276 * @param plotter The plotter 277 */ 278 public void setItemPlotter( final ItemPlotter<O, Float[], MBFImage> plotter ) 279 { 280 this.plotter = plotter; 281 } 282 283 /** 284 * Provides access to the underlying axes renderer so that various changes 285 * can be made to the visualisation. 286 * 287 * @return The axes renderer. 288 */ 289 public AxesRenderer2D<Float[], MBFImage> getAxesRenderer() 290 { 291 return this.axesRenderer2D; 292 } 293 294 /** 295 * Clear the data list. 296 */ 297 public void clearData() 298 { 299 synchronized( this.data ) 300 { 301 this.data.clear(); 302 } 303 } 304 305 /** 306 * {@inheritDoc} 307 * @see org.openimaj.vis.VisualisationImpl#setData(java.lang.Object) 308 */ 309 @Override 310 public void setData( final List<LocatedObject<O>> data ) 311 { 312 super.setData( data ); 313 this.validateData(); 314 } 315 316 /** 317 * Set up the min/max of the axes based on the data. 318 */ 319 protected void validateData() 320 { 321 if( this.autoScaleAxes && this.data.size() > 0 ) 322 { 323 double minX = Double.MAX_VALUE; 324 double maxX = -Double.MAX_VALUE; 325 double minY = Double.MAX_VALUE; 326 double maxY = -Double.MAX_VALUE; 327 for( final LocatedObject<O> o : this.data) 328 { 329 minX = Math.min( minX, o.x ); 330 maxX = Math.max( maxX, o.x ); 331 minY = Math.min( minY, o.y ); 332 maxY = Math.max( maxY, o.y ); 333 } 334 335 this.axesRenderer2D.setMaxXValue( maxX ); 336 this.axesRenderer2D.setMinXValue( minX ); 337 this.axesRenderer2D.setMaxYValue( maxY ); 338 this.axesRenderer2D.setMinYValue( minY ); 339 this.axesRenderer2D.precalc(); 340 341// System.out.println( "XYPlotVis.validateData(): max x: "+maxX+ 342// ", min x: "+minX+", max y: "+maxY+", min y: "+minY ); 343 } 344 } 345 346 /** 347 * @return the autoScaleAxes 348 */ 349 public boolean isAutoScaleAxes() 350 { 351 return this.autoScaleAxes; 352 } 353 354 /** 355 * @param autoScaleAxes the autoScaleAxes to set 356 */ 357 public void setAutoScaleAxes( final boolean autoScaleAxes ) 358 { 359 this.autoScaleAxes = autoScaleAxes; 360 } 361 362 /** 363 * @return the autoPositionXAxis 364 */ 365 public boolean isAutoPositionXAxis() 366 { 367 return this.autoPositionXAxis; 368 } 369 370 /** 371 * @param autoPositionXAxis the autoPositionXAxis to set 372 */ 373 public void setAutoPositionXAxis( final boolean autoPositionXAxis ) 374 { 375 this.autoPositionXAxis = autoPositionXAxis; 376 } 377 378 /** 379 * @return the renderAxesLast 380 */ 381 public boolean isRenderAxesLast() 382 { 383 return this.renderAxesLast; 384 } 385 386 /** 387 * @param renderAxesLast the renderAxesLast to set 388 */ 389 public void setRenderAxesLast( final boolean renderAxesLast ) 390 { 391 this.renderAxesLast = renderAxesLast; 392 } 393}