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}