View Javadoc

1   /*
2    * @(#) BoxAndWhiskerPlot.java Sep 24, 2003
3    * 
4    * Copyright (c) 2003 Delft University of Technology Jaffalaan 5, 2628 BX Delft,
5    * the Netherlands All rights reserved.
6    * 
7    * This software is proprietary information of Delft University of Technology
8    * The code is published under the General Public License
9    */
10  
11  package nl.tudelft.simulation.jstats.charts.boxAndWhisker;
12  
13  import java.awt.Color;
14  import java.awt.Font;
15  import java.awt.Graphics2D;
16  import java.awt.font.FontRenderContext;
17  import java.awt.geom.Rectangle2D;
18  import java.text.NumberFormat;
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.List;
22  
23  import nl.tudelft.simulation.event.EventInterface;
24  import nl.tudelft.simulation.event.EventListenerInterface;
25  import nl.tudelft.simulation.jstats.statistics.Tally;
26  
27  import org.jfree.chart.event.PlotChangeEvent;
28  import org.jfree.chart.plot.Plot;
29  import org.jfree.chart.plot.PlotRenderingInfo;
30  import org.jfree.chart.plot.PlotState;
31  
32  /***
33   * The Summary chart class defines a summary chart. <br>
34   * (c) copyright 2003 <a href="http://www.simulation.tudelft.nl">Delft
35   * University of Technology </a>, the Netherlands. <br>
36   * See for project information <a
37   * href="http://www.simulation.tudelft.nl">www.simulation.tudelft.nl </a> <br>
38   * License of use: <a href="http://www.gnu.org/copyleft/gpl.html">General Public
39   * License (GPL) </a>, no warranty <br>
40   * 
41   * @version 2.0 21.09.2003 <br>
42   * @author <a href="http://www.tbm.tudelft.nl/webstaf/peterja/index.htm">Peter
43   *         Jacobs </a>, <a
44   *         href="http://www.tbm.tudelft.nl/webstaf/alexandv/index.htm">Alexander
45   *         Verbraeck </a>
46   */
47  public class BoxAndWhiskerPlot extends Plot implements EventListenerInterface
48  {
49  	/*** BORDER_SIZE refers to the width of the border on the panel */
50  	public static final short BORDER_SIZE = 50;
51  
52  	/*** PLOT_TYPE refers to the plot type */
53  	public static final String PLOT_TYPE = "SUMMARY_PLOT";
54  
55  	/*** FONT defines the font of the plot */
56  	public static final Font FONT = new Font("SansSerif", Font.PLAIN, 10);
57  
58  	/*** TITLE_FONT defines the font of the plot */
59  	public static final Font TITLE_FONT = new Font("SansSerif", Font.BOLD, 15);
60  
61  	/*** target is the tally to represent */
62  	protected Tally[] tallies = new Tally[0];
63  
64  	/*** formatter formats the text */
65  	protected NumberFormat formatter = NumberFormat.getInstance();
66  
67  	/***
68  	 * constructs a new BoxAndWhiskerPlot
69  	 */
70  	public BoxAndWhiskerPlot()
71  	{
72  		super();
73  	}
74  
75  	/***
76  	 * adds a tally to the array of targets;
77  	 * 
78  	 * @param tally the tally to be summarized
79  	 */
80  	public synchronized void add(final Tally tally)
81  	{
82  		tally.addListener(this, Tally.SAMPLE_MEAN_EVENT, false);
83  		List list = new ArrayList(Arrays.asList(this.tallies));
84  		list.add(tally);
85  		this.tallies = (Tally[]) list.toArray(new Tally[list.size()]);
86  	}
87  
88  	/***
89  	 * @see org.jfree.chart.plot.Plot#getPlotType()
90  	 */
91  	public String getPlotType()
92  	{
93  		return PLOT_TYPE;
94  	}
95  
96  	/***
97  	 * @see nl.tudelft.simulation.event.EventListenerInterface
98  	 *      #notify(nl.tudelft.simulation.event.EventInterface)
99  	 */
100 	public void notify(final EventInterface event)
101 	{
102 		this.notifyListeners(new PlotChangeEvent(this));
103 	}
104 
105 	/*** ************ PRIVATE METHODS *********************** */
106 	/***
107 	 * computes the extent of the targets
108 	 * 
109 	 * @param tallies the range of tallies
110 	 * @return double[min,max]
111 	 */
112 	private static double[] extent(final Tally[] tallies)
113 	{
114 		double[] result = {Double.MAX_VALUE, -Double.MAX_VALUE};
115 		for (int i = 0; i < tallies.length; i++)
116 		{
117 			if (tallies[i].getMin() < result[0])
118 			{
119 				result[0] = tallies[i].getMin();
120 			}
121 			if (tallies[i].getMax() > result[1])
122 			{
123 				result[1] = tallies[i].getMax();
124 			}
125 		}
126 		return result;
127 	}
128 
129 	/***
130 	 * determines the borders on the left and right side of the tally
131 	 * 
132 	 * @param g2 the graphics object
133 	 * @param context the context
134 	 * @param tallies tallies
135 	 * @return double[] the extent
136 	 */
137 	private double[] borders(final Graphics2D g2,
138 			final FontRenderContext context, final Tally[] tallies)
139 	{
140 		double[] result = {0, 0};
141 		for (int i = 0; i < tallies.length; i++)
142 		{
143 			double left = g2.getFont().getStringBounds(
144 					this.formatter.format(tallies[i].getMin()), context)
145 					.getWidth();
146 			double rigth = g2.getFont().getStringBounds(
147 					this.formatter.format(tallies[i].getMax()), context)
148 					.getWidth();
149 			if (left > result[0])
150 			{
151 				result[0] = left;
152 			}
153 			if (rigth > result[1])
154 			{
155 				result[1] = rigth;
156 			}
157 		}
158 		result[0] = result[0] + 3;
159 		result[1] = result[1] + 3;
160 		return result;
161 	}
162 
163 	/***
164 	 * returns the bounding box
165 	 * 
166 	 * @param word the word
167 	 * @param context the context
168 	 * @return Rectangle2D the bounds
169 	 */
170 	private Rectangle2D getBounds(final String word,
171 			final FontRenderContext context)
172 	{
173 		return FONT.getStringBounds(word, context);
174 	}
175 
176 	/***
177 	 * fills a rectangle
178 	 * 
179 	 * @param g2 the graphics object
180 	 * @param rectangle the area
181 	 * @param color the color
182 	 */
183 	private void fillRectangle(final Graphics2D g2,
184 			final Rectangle2D rectangle, final Color color)
185 	{
186 		g2.setColor(color);
187 		g2.fillRect((int) rectangle.getX(), (int) rectangle.getY(),
188 				(int) rectangle.getWidth(), (int) rectangle.getHeight());
189 	}
190 
191 	/***
192 	 * paints a tally
193 	 * 
194 	 * @param g2 the graphics object
195 	 * @param rectangle the rectangle on which to paint
196 	 * @param tally the tally
197 	 * @param leftX the lowest real value
198 	 * @param leftBorder the left border
199 	 * @param scale the scale
200 	 */
201 	private void paintTally(final Graphics2D g2, final Rectangle2D rectangle,
202 			final Tally tally, final double leftX, final double leftBorder,
203 			final double scale)
204 	{
205 		this.fillRectangle(g2, rectangle, Color.WHITE);
206 		g2.setColor(Color.BLACK);
207 		g2.setFont(TITLE_FONT);
208 		g2.drawString(tally.getDescription(), (int) Math.round(leftBorder
209 				+ 0.5
210 				* (rectangle.getWidth() - leftBorder - 20)
211 				- 0.5
212 				* this.getBounds(tally.getDescription(),
213 						g2.getFontRenderContext()).getWidth()),
214 				25 + (int) rectangle.getY());
215 		g2.setFont(FONT);
216 		g2
217 				.drawRect((int) rectangle.getX() - 1,
218 						(int) rectangle.getY() - 1,
219 						(int) rectangle.getWidth() + 2, (int) rectangle
220 								.getHeight() + 2);
221 		int tallyMin = (int) Math.round(rectangle.getX()
222 				+ (tally.getMin() - leftX) * scale + leftBorder);
223 		int tallyMax = (int) Math.round(rectangle.getX()
224 				+ (tally.getMax() - leftX) * scale + leftBorder);
225 		int middle = (int) Math.round(rectangle.getY() + 0.5
226 				* rectangle.getHeight());
227 		String label = this.formatter.format(tally.getMin());
228 		g2.drawString(label, (int) Math.round(tallyMin - 3
229 				- this.getBounds(label, g2.getFontRenderContext()).getWidth()),
230 				(int) Math.round(middle
231 						+ 0.5
232 						* this.getBounds(label, g2.getFontRenderContext())
233 								.getHeight()));
234 		label = this.formatter.format(tally.getMax());
235 		g2
236 				.drawString(label, tallyMax + 3, (int) Math.round(middle
237 						+ 0.5
238 						* this.getBounds(label, g2.getFontRenderContext())
239 								.getHeight()));
240 		g2.drawLine(tallyMin, middle + 6, tallyMin, middle - 6);
241 		g2.drawLine(tallyMin, middle, tallyMax, middle);
242 		g2.drawLine(tallyMax, middle + 6, tallyMax, middle - 6);
243 		double[] confidence = tally.getConfidenceInterval(0.05);
244 		int middleX = (int) Math.round((tally.getSampleMean() - leftX) * scale
245 				+ tallyMin);
246 		g2.fillRect(middleX, middle - 6, 2, 12);
247 		label = this.formatter.format(tally.getSampleMean());
248 		Rectangle2D bounds = this.getBounds(label, g2.getFontRenderContext());
249 		g2.drawString(label, (int) Math
250 				.round(middleX - 0.5 * bounds.getWidth()), Math
251 				.round(middle - 8));
252 		if (confidence != null)
253 		{
254 			int confX = (int) Math.round((confidence[0] - leftX) * scale
255 					+ tallyMin);
256 			int confWidth = (int) Math.round((confidence[1] - confidence[0])
257 					* scale);
258 			g2.fillRect(confX, middle - 2, confWidth, 4);
259 			label = this.formatter.format(confidence[0]);
260 			bounds = this.getBounds(label, g2.getFontRenderContext());
261 			g2.drawString(label, (int) Math.round(confX - bounds.getWidth()),
262 					(int) Math.round(middle + 8 + bounds.getHeight()));
263 			label = this.formatter.format(confidence[1]);
264 			bounds = this.getBounds(label, g2.getFontRenderContext());
265 			g2.drawString(label, Math.round(confX + confWidth), (int) Math
266 					.round(middle + 8 + bounds.getHeight()));
267 		}
268 	}
269 
270 	/***
271 	 * @see org.jfree.chart.plot.Plot#draw(java.awt.Graphics2D,
272 	 *      java.awt.geom.Rectangle2D, org.jfree.chart.plot.PlotState,
273 	 *      org.jfree.chart.plot.PlotRenderingInfo)
274 	 */
275 	public void draw(final Graphics2D g2, final Rectangle2D rectangle,
276 			final PlotState plotState, final PlotRenderingInfo plotRenderingInfo)
277 	{
278 		g2.setBackground(Color.WHITE);
279 		double height = Math.min(rectangle.getHeight() / this.tallies.length
280 				* 1.0, rectangle.getHeight());
281 		double[] extent = BoxAndWhiskerPlot.extent(this.tallies);
282 		double[] border = this.borders(g2, g2.getFontRenderContext(),
283 				this.tallies);
284 		double scale = (0.85 * rectangle.getWidth() - 10 - border[0] - border[1])
285 				/ ((extent[1] - extent[0]) * 1.0);
286 		for (int i = 0; i < this.tallies.length; i++)
287 		{
288 			g2.setFont(FONT);
289 			Rectangle2D area = new Rectangle2D.Double(rectangle.getX() + 0.15
290 					* rectangle.getWidth(), rectangle.getY() + i * height + 3,
291 					0.85 * rectangle.getWidth() - 10, 0.75 * height - 3);
292 			this.paintTally(g2, area, this.tallies[i], extent[0], border[0],
293 					scale);
294 		}
295 	}
296 }