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.ColourMap;
040import org.openimaj.image.colour.RGBColour;
041import org.openimaj.image.renderer.MBFImageRenderer;
042import org.openimaj.image.renderer.RenderHints;
043import org.openimaj.image.typography.hershey.HersheyFont;
044import org.openimaj.image.typography.hershey.HersheyFontStyle;
045import org.openimaj.math.geometry.point.Point2d;
046import org.openimaj.math.geometry.shape.Circle;
047import org.openimaj.math.geometry.shape.Rectangle;
048import org.openimaj.vis.general.DotPlotVisualisation.ColouredDot;
049import org.openimaj.vis.general.LabelledPointVisualisation.LabelledDot;
050
051/**
052 *      Plots dots with a label. This can be used as a
053 *      visualisation in itself or used as an {@link ItemPlotter} in other visualisations.
054 *
055 *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
056 *  @created 3 Jun 2013
057 */
058public class LabelledPointVisualisation extends XYPlotVisualisation<LabelledDot>
059        implements ItemPlotter<LabelledDot,Float[],MBFImage>
060{
061        /**
062         *
063         *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
064         *  @created 11 Jun 2013
065         */
066        public static class LabelledDot extends ColouredDot
067        {
068                /** The label */
069                public String label;
070
071                /**
072                 *      Create a labelled dot.
073                 *      @param l The label
074                 *      @param s The size
075                 */
076                public LabelledDot( final String l, final double s )
077                {
078                        super( s, RGBColour.RED );
079                        this.label = l;
080                }
081
082                /**
083                 *      Create a labelled dot.
084                 *      @param l The label
085                 *      @param s The size
086                 *      @param colour The colour
087                 */
088                public LabelledDot( final String l, final double s, final Float[] colour )
089                {
090                        super( s, colour );
091                        this.label = l;
092                }
093}
094
095        /** */
096        private static final long serialVersionUID = 1L;
097
098        /** Whether to avoid drawing text that will overlap other text */
099        private boolean avoidOverlaps = true;
100
101        /** The bounds of the labels that have already been drawn */
102        private final List<Rectangle> bounds = new ArrayList<Rectangle>();
103
104        /**
105         *      Default constructor
106         */
107        public LabelledPointVisualisation()
108        {
109                super( null );
110                this.setItemPlotter( this );
111        }
112
113        /**
114         *      Constructor that takes the width and height of the visualisation
115         *
116         *      @param width The width of the visualisation in pixels
117         *      @param height The height of the visualisation in pixels
118         */
119        public LabelledPointVisualisation( final int width, final int height )
120        {
121                super( width, height, null );
122                this.setItemPlotter( this );
123        }
124
125        /**
126         *      {@inheritDoc}
127         *      @see org.openimaj.vis.general.ItemPlotter#renderRestarting()
128         */
129        @Override
130        public void renderRestarting()
131        {
132                this.bounds.clear();
133        }
134
135        /**
136         *      {@inheritDoc}
137         *      @see org.openimaj.vis.general.ItemPlotter#plotObject(org.openimaj.image.Image, org.openimaj.vis.general.XYPlotVisualisation.LocatedObject, org.openimaj.vis.general.AxesRenderer2D)
138         */
139        @Override
140        public void plotObject( final MBFImage visImage,
141                        final XYPlotVisualisation.LocatedObject<LabelledDot> object,
142                        final AxesRenderer2D<Float[],MBFImage> renderer )
143        {
144                // Get the position where we're going to place the dot
145                Point2d pos = renderer.calculatePosition( object.x, object.y );
146
147                final MBFImageRenderer ir = visImage.createRenderer( RenderHints.ANTI_ALIASED );
148
149                // Draw the dot
150                ir.drawShapeFilled(
151                                new Circle( pos,
152                                        (float)(renderer.scaleDimensions( object.object.size, object.object.size )[0] ) ),
153                                        object.object.colour );
154
155                // Get the position where we're going the place the text
156                pos = renderer.calculatePosition( object.x + object.object.size, object.y );
157
158                // Create the font and font style
159                final HersheyFont f = HersheyFont.TIMES_MEDIUM;
160                final HersheyFontStyle<Float[]> fs = f.createStyle( visImage.createRenderer() );
161                fs.setFontSize( 14 );
162
163                // Calculate the bounding box of the text we're going to draw.
164                final Rectangle b = fs.getRenderer( visImage.createRenderer() ).getSize(
165                                object.object.label, fs );
166
167                // Bounding box is 0,0,width,height, so move it into position.
168                b.translate( 0, b.height );
169                b.translate( pos.getX() + 4, pos.getY() );
170//              System.out.println( b );
171
172                boolean overlap = false;
173                for( final Rectangle bb : this.bounds ) {
174                        if( bb.isOverlapping( b ) ) {
175                                overlap = true; break;
176                        }
177                }
178
179                this.bounds.add( b );
180
181                if( !overlap )
182                        ir.drawText(
183                                        object.object.label, (int)b.x, (int)b.y + (int)b.height,
184                                        f, 14, object.object.colour );
185
186        }
187
188        /**
189         *      @return the avoidOverlaps
190         */
191        public boolean isAvoidOverlaps()
192        {
193                return this.avoidOverlaps;
194        }
195
196        /**
197         *      @param avoidOverlaps the avoidOverlaps to set
198         */
199        public void setAvoidOverlaps( final boolean avoidOverlaps )
200        {
201                this.avoidOverlaps = avoidOverlaps;
202        }
203
204        /**
205         *      Main method to demonstrate the vis.
206         *      @param args command-line args (not used)
207         */
208        public static void main( final String[] args )
209        {
210                final LabelledPointVisualisation dpv = new LabelledPointVisualisation( 1000, 600 );
211                dpv.getAxesRenderer().setxMajorTickSpacing( 0.2 );
212                dpv.getAxesRenderer().setxMinorTickSpacing( 0.05 );
213                dpv.getAxesRenderer().setyMajorTickSpacing( 0.2 );
214                dpv.getAxesRenderer().setyMinorTickSpacing( 0.05 );
215
216                for( int i = 0; i < 10; i++ )
217                {
218                        final double v = Math.random()/10d;
219                        dpv.addPoint( (Math.random()-0.5)*2, (Math.random()-0.5)*2,
220                                        new LabelledDot( "Dot "+i, v, ColourMap.Cool.apply( (float)v*10f ) ) );
221                }
222
223                dpv.updateVis();
224                dpv.showWindow("Labelled Point Vis" );
225        }
226}