1
2
3
4
5
6
7
8
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 }