1. /* ===========================================================
  2. * JFreeChart : a free chart library for the Java(tm) platform
  3. * ===========================================================
  4. *
  5. * (C) Copyright 2000-2004, by Object Refinery Limited and Contributors.
  6. *
  7. * Project Info: http://www.jfree.org/jfreechart/index.html
  8. *
  9. * This library is free software; you can redistribute it and/or modify it under the terms
  10. * of the GNU Lesser General Public License as published by the Free Software Foundation;
  11. * either version 2.1 of the License, or (at your option) any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  14. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  15. * See the GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License along with this
  18. * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  19. * Boston, MA 02111-1307, USA.
  20. *
  21. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  22. * in the United States and other countries.]
  23. *
  24. * ----------------------------
  25. * XYBoxAndWhiskerRenderer.java
  26. * ----------------------------
  27. * (C) Copyright 2003, 2004, by David Browning and Contributors.
  28. *
  29. * Original Author: David Browning (for Australian Institute of Marine Science);
  30. * Contributor(s): David Gilbert (for Object Refinery Limited);
  31. *
  32. * $Id: XYBoxAndWhiskerRenderer.java,v 1.4 2004/11/27 17:06:18 mungady Exp $
  33. *
  34. * Changes
  35. * -------
  36. * 05-Aug-2003 : Version 1, contributed by David Browning. Based on code in the
  37. * CandlestickRenderer class. Additional modifications by David Gilbert to
  38. * make the code work with 0.9.10 changes (DG);
  39. * 08-Aug-2003 : Updated some of the Javadoc
  40. * Allowed BoxAndwhiskerDataset Average value to be null - the average value is an
  41. * AIMS requirement
  42. * Allow the outlier and farout coefficients to be set -
  43. * though at the moment this only affects the calculation of farouts.
  44. * Added artifactPaint variable and setter/getter
  45. * 12-Aug-2003 Rewrote code to sort out and process outliers to take advantage of changes in
  46. * DefaultBoxAndWhiskerDataset
  47. * Added a limit of 10% for width of box should no width be specified...
  48. * Maybe this should be setable???
  49. * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
  50. * 08-Sep-2003 : Changed ValueAxis API (DG);
  51. * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  52. * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
  53. * 23-Apr-2004 : Added fillBox attribute, extended equals() method and fixed serialization
  54. * issue (DG);
  55. * 29-Apr-2004 : Fixed problem with drawing upper and lower shadows - bug id 944011 (DG);
  56. * 15-Jul-2004 : Switched getX() with getXValue() and getY() with getYValue() (DG);
  57. * 01-Oct-2004 : Renamed 'paint' --> 'boxPaint' to avoid conflict with inherited attribute (DG);
  58. *
  59. * DO NOT USE drawHorizontalItem() - IT IS INCOMPLETE
  60. * TO EXPERIMENT, USE drawVerticalItem()
  61. *
  62. */
  63. package org.jfree.chart.renderer.xy;
  64. import java.awt.Color;
  65. import java.awt.Graphics2D;
  66. import java.awt.Paint;
  67. import java.awt.Shape;
  68. import java.awt.Stroke;
  69. import java.awt.geom.Ellipse2D;
  70. import java.awt.geom.Line2D;
  71. import java.awt.geom.Point2D;
  72. import java.awt.geom.Rectangle2D;
  73. import java.io.IOException;
  74. import java.io.ObjectInputStream;
  75. import java.io.ObjectOutputStream;
  76. import java.io.Serializable;
  77. import java.util.ArrayList;
  78. import java.util.Collections;
  79. import java.util.Iterator;
  80. import java.util.List;
  81. import org.jfree.chart.axis.ValueAxis;
  82. import org.jfree.chart.entity.EntityCollection;
  83. import org.jfree.chart.entity.XYItemEntity;
  84. import org.jfree.chart.event.RendererChangeEvent;
  85. import org.jfree.chart.labels.BoxAndWhiskerXYToolTipGenerator;
  86. import org.jfree.chart.labels.XYToolTipGenerator;
  87. import org.jfree.chart.plot.CrosshairState;
  88. import org.jfree.chart.plot.PlotOrientation;
  89. import org.jfree.chart.plot.PlotRenderingInfo;
  90. import org.jfree.chart.plot.XYPlot;
  91. import org.jfree.chart.renderer.Outlier;
  92. import org.jfree.chart.renderer.OutlierList;
  93. import org.jfree.chart.renderer.OutlierListCollection;
  94. import org.jfree.data.statistics.BoxAndWhiskerXYDataset;
  95. import org.jfree.data.xy.XYDataset;
  96. import org.jfree.io.SerialUtilities;
  97. import org.jfree.ui.RectangleEdge;
  98. import org.jfree.util.ObjectUtilities;
  99. import org.jfree.util.PublicCloneable;
  100. /**
  101. * A renderer that draws box-and-whisker items on an {@link XYPlot}. This renderer requires a
  102. * {@link BoxAndWhiskerXYDataset}).
  103. * <P>
  104. * This renderer does not include any code to calculate the crosshair point.
  105. *
  106. * @author David Browning
  107. */
  108. public class XYBoxAndWhiskerRenderer extends AbstractXYItemRenderer
  109. implements XYItemRenderer,
  110. Cloneable,
  111. PublicCloneable,
  112. Serializable {
  113. /** The box width. */
  114. private double boxWidth;
  115. /** The paint used to fill the box. */
  116. private transient Paint boxPaint;
  117. /** A flag that controls whether or not the box is filled. */
  118. private boolean fillBox;
  119. /**
  120. * The paint used to draw various artifacts such as outliers, farout symbol, average
  121. * ellipse and median line.
  122. */
  123. private transient Paint artifactPaint = Color.black;
  124. /**
  125. * Creates a new renderer for box and whisker charts.
  126. */
  127. public XYBoxAndWhiskerRenderer() {
  128. this(-1.0);
  129. }
  130. /**
  131. * Creates a new renderer for box and whisker charts.
  132. * <P>
  133. * Use -1 for the box width if you prefer the width to be calculated automatically.
  134. *
  135. * @param boxWidth the box width.
  136. */
  137. public XYBoxAndWhiskerRenderer(double boxWidth) {
  138. super();
  139. this.boxWidth = boxWidth;
  140. this.boxPaint = Color.green;
  141. this.fillBox = true;
  142. setToolTipGenerator(new BoxAndWhiskerXYToolTipGenerator());
  143. }
  144. /**
  145. * Returns the width of each box.
  146. *
  147. * @return The box width.
  148. */
  149. public double getBoxWidth() {
  150. return this.boxWidth;
  151. }
  152. /**
  153. * Sets the box width.
  154. * <P>
  155. * If you set the width to a negative value, the renderer will calculate
  156. * the box width automatically based on the space available on the chart.
  157. *
  158. * @param width the width.
  159. */
  160. public void setBoxWidth(double width) {
  161. if (width != this.boxWidth) {
  162. this.boxWidth = width;
  163. notifyListeners(new RendererChangeEvent(this));
  164. }
  165. }
  166. /**
  167. * Returns the paint used to fill boxes.
  168. *
  169. * @return The paint (possibly <code>null</code>).
  170. */
  171. public Paint getBoxPaint() {
  172. return this.boxPaint;
  173. }
  174. /**
  175. * Sets the paint used to fill boxes and sends a {@link RendererChangeEvent}
  176. * to all registered listeners.
  177. *
  178. * @param paint the paint (<code>null</code> permitted).
  179. */
  180. public void setBoxPaint(Paint paint) {
  181. this.boxPaint = paint;
  182. notifyListeners(new RendererChangeEvent(this));
  183. }
  184. /**
  185. * Returns the flag that controls whether or not the box is filled.
  186. *
  187. * @return A boolean.
  188. */
  189. public boolean getFillBox() {
  190. return this.fillBox;
  191. }
  192. /**
  193. * Sets the flag that controls whether or not the box is filled and sends a
  194. * {@link RendererChangeEvent} to all registered listeners.
  195. *
  196. * @param flag the flag.
  197. */
  198. public void setFillBox(boolean flag) {
  199. this.fillBox = flag;
  200. notifyListeners(new RendererChangeEvent(this));
  201. }
  202. /**
  203. * Returns the paint used to paint the various artifacts such as outliers, farout symbol,
  204. * median line and the averages ellipse.
  205. *
  206. * @return The paint.
  207. */
  208. public Paint getArtifactPaint() {
  209. return this.artifactPaint;
  210. }
  211. /**
  212. * Sets the paint used to paint the various artifacts such as outliers, farout symbol,
  213. * median line and the averages ellipse.
  214. *
  215. * @param artifactPaint the paint.
  216. */
  217. public void setArtifactPaint(Paint artifactPaint) {
  218. this.artifactPaint = artifactPaint;
  219. }
  220. /**
  221. * Draws the visual representation of a single data item.
  222. *
  223. * @param g2 the graphics device.
  224. * @param state the renderer state.
  225. * @param dataArea the area within which the plot is being drawn.
  226. * @param info collects info about the drawing.
  227. * @param plot the plot (can be used to obtain standard color information etc).
  228. * @param domainAxis the domain axis.
  229. * @param rangeAxis the range axis.
  230. * @param dataset the dataset.
  231. * @param series the series index (zero-based).
  232. * @param item the item index (zero-based).
  233. * @param crosshairState crosshair information for the plot (<code>null</code> permitted).
  234. * @param pass the pass index.
  235. */
  236. public void drawItem(Graphics2D g2,
  237. XYItemRendererState state,
  238. Rectangle2D dataArea,
  239. PlotRenderingInfo info,
  240. XYPlot plot,
  241. ValueAxis domainAxis,
  242. ValueAxis rangeAxis,
  243. XYDataset dataset,
  244. int series,
  245. int item,
  246. CrosshairState crosshairState,
  247. int pass) {
  248. PlotOrientation orientation = plot.getOrientation();
  249. setPaint(getItemPaint(series, item));
  250. if (orientation == PlotOrientation.HORIZONTAL) {
  251. drawHorizontalItem(
  252. g2, dataArea, info, plot, domainAxis, rangeAxis,
  253. dataset, series, item, crosshairState, pass
  254. );
  255. }
  256. else if (orientation == PlotOrientation.VERTICAL) {
  257. drawVerticalItem(
  258. g2, dataArea, info, plot, domainAxis, rangeAxis,
  259. dataset, series, item, crosshairState, pass
  260. );
  261. }
  262. }
  263. /**
  264. * Draws the visual representation of a single data item.
  265. *
  266. * @param g2 the graphics device.
  267. * @param dataArea the area within which the plot is being drawn.
  268. * @param info collects info about the drawing.
  269. * @param plot the plot (can be used to obtain standard color information etc).
  270. * @param domainAxis the domain axis.
  271. * @param rangeAxis the range axis.
  272. * @param dataset the dataset.
  273. * @param series the series index (zero-based).
  274. * @param item the item index (zero-based).
  275. * @param crosshairState crosshair information for the plot (<code>null</code> permitted).
  276. * @param pass the pass index.
  277. */
  278. public void drawHorizontalItem(Graphics2D g2,
  279. Rectangle2D dataArea,
  280. PlotRenderingInfo info,
  281. XYPlot plot,
  282. ValueAxis domainAxis,
  283. ValueAxis rangeAxis,
  284. XYDataset dataset,
  285. int series,
  286. int item,
  287. CrosshairState crosshairState,
  288. int pass) {
  289. // setup for collecting optional entity info...
  290. EntityCollection entities = null;
  291. if (info != null) {
  292. entities = info.getOwner().getEntityCollection();
  293. }
  294. BoxAndWhiskerXYDataset boxAndWhiskerData = (BoxAndWhiskerXYDataset) dataset;
  295. Number x = boxAndWhiskerData.getX(series, item);
  296. Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item);
  297. Number yMin = boxAndWhiskerData.getMinRegularValue(series, item);
  298. Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item);
  299. Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item);
  300. double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea, plot.getDomainAxisEdge());
  301. RectangleEdge location = plot.getRangeAxisEdge();
  302. double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea, location);
  303. double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea, location);
  304. double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(), dataArea, location);
  305. double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(), dataArea, location);
  306. double exactCandleWidth = getBoxWidth();
  307. double thisCandleWidth = exactCandleWidth;
  308. if (exactCandleWidth <= 0.0) {
  309. int itemCount = boxAndWhiskerData.getItemCount(series);
  310. exactCandleWidth = (dataArea.getHeight()) / itemCount * 4.5 / 7;
  311. if (exactCandleWidth < 1) {
  312. exactCandleWidth = 1;
  313. }
  314. thisCandleWidth = exactCandleWidth;
  315. if (thisCandleWidth < 3) {
  316. thisCandleWidth = 3;
  317. }
  318. }
  319. Stroke s = getItemStroke(series, item);
  320. g2.setStroke(s);
  321. // draw the upper shadow
  322. if ((yyMax > yyQ1Median) && (yyMax > yyQ3Median)) {
  323. g2.draw(new Line2D.Double(yyMax, xx, Math.max(yyQ1Median, yyQ3Median), xx));
  324. }
  325. // draw the lower shadow
  326. if ((yyMin < yyQ1Median) && (yyMin < yyQ3Median)) {
  327. g2.draw(new Line2D.Double(yyMin, xx, Math.min(yyQ1Median, yyQ3Median), xx));
  328. }
  329. // draw the body
  330. Shape box = null;
  331. if (yyQ1Median < yyQ3Median) {
  332. box = new Rectangle2D.Double(
  333. yyQ1Median, xx - thisCandleWidth / 2, yyQ3Median - yyQ1Median, thisCandleWidth
  334. );
  335. }
  336. else {
  337. box = new Rectangle2D.Double(
  338. yyQ3Median, xx - thisCandleWidth / 2, yyQ1Median - yyQ3Median, thisCandleWidth
  339. );
  340. if (getBoxPaint() != null) {
  341. g2.setPaint(getBoxPaint());
  342. }
  343. if (this.fillBox) {
  344. g2.fill(box);
  345. }
  346. g2.draw(box);
  347. }
  348. // add an entity for the item...
  349. if (entities != null) {
  350. String tip = null;
  351. XYToolTipGenerator generator = getToolTipGenerator(series, item);
  352. if (generator != null) {
  353. tip = generator.generateToolTip(dataset, series, item);
  354. }
  355. String url = null;
  356. if (getURLGenerator() != null) {
  357. url = getURLGenerator().generateURL(dataset, series, item);
  358. }
  359. XYItemEntity entity = new XYItemEntity(box, dataset, series, item, tip, url);
  360. entities.add(entity);
  361. }
  362. }
  363. /**
  364. * Draws the visual representation of a single data item.
  365. *
  366. * @param g2 the graphics device.
  367. * @param dataArea the area within which the plot is being drawn.
  368. * @param info collects info about the drawing.
  369. * @param plot the plot (can be used to obtain standard color information etc).
  370. * @param domainAxis the domain axis.
  371. * @param rangeAxis the range axis.
  372. * @param dataset the dataset.
  373. * @param series the series index (zero-based).
  374. * @param item the item index (zero-based).
  375. * @param crosshairState crosshair information for the plot (<code>null</code> permitted).
  376. * @param pass the pass index.
  377. */
  378. public void drawVerticalItem(Graphics2D g2,
  379. Rectangle2D dataArea,
  380. PlotRenderingInfo info,
  381. XYPlot plot,
  382. ValueAxis domainAxis,
  383. ValueAxis rangeAxis,
  384. XYDataset dataset,
  385. int series,
  386. int item,
  387. CrosshairState crosshairState,
  388. int pass) {
  389. // setup for collecting optional entity info...
  390. EntityCollection entities = null;
  391. if (info != null) {
  392. entities = info.getOwner().getEntityCollection();
  393. }
  394. BoxAndWhiskerXYDataset boxAndWhiskerData = (BoxAndWhiskerXYDataset) dataset;
  395. Number x = boxAndWhiskerData.getX(series, item);
  396. Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item);
  397. Number yMin = boxAndWhiskerData.getMinRegularValue(series, item);
  398. Number yMedian = boxAndWhiskerData.getMedianValue(series, item);
  399. Number yAverage = boxAndWhiskerData.getMeanValue(series, item);
  400. Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item);
  401. Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item);
  402. List yOutliers = boxAndWhiskerData.getOutliers(series, item);
  403. double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea, plot.getDomainAxisEdge());
  404. RectangleEdge location = plot.getRangeAxisEdge();
  405. double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea, location);
  406. double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea, location);
  407. double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(), dataArea, location);
  408. double yyAverage = 0.0;
  409. if (yAverage != null) {
  410. yyAverage = rangeAxis.valueToJava2D(yAverage.doubleValue(), dataArea, location);
  411. }
  412. double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(), dataArea, location);
  413. double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(), dataArea, location);
  414. double yyOutlier;
  415. double exactBoxWidth = getBoxWidth();
  416. double width = exactBoxWidth;
  417. double dataAreaX = dataArea.getMaxX() - dataArea.getMinX();
  418. double maxBoxPercent = 0.1;
  419. double maxBoxWidth = dataAreaX * maxBoxPercent;
  420. if (exactBoxWidth <= 0.0) {
  421. int itemCount = boxAndWhiskerData.getItemCount(series);
  422. exactBoxWidth = dataAreaX / itemCount * 4.5 / 7;
  423. if (exactBoxWidth < 3) {
  424. width = 3;
  425. }
  426. else if (exactBoxWidth > maxBoxWidth) {
  427. width = maxBoxWidth;
  428. }
  429. else {
  430. width = exactBoxWidth;
  431. }
  432. }
  433. Paint p = getBoxPaint();
  434. if (p != null) {
  435. g2.setPaint(p);
  436. }
  437. Stroke s = getItemStroke(series, item);
  438. g2.setStroke(s);
  439. // draw the upper shadow
  440. g2.draw(new Line2D.Double(xx, yyMax, xx, yyQ3Median));
  441. g2.draw(new Line2D.Double(xx - width / 2, yyMax, xx + width / 2, yyMax));
  442. // draw the lower shadow
  443. g2.draw(new Line2D.Double(xx, yyMin, xx, yyQ1Median));
  444. g2.draw(new Line2D.Double(xx - width / 2, yyMin, xx + width / 2, yyMin));
  445. // draw the body
  446. Shape box = null;
  447. if (yyQ1Median > yyQ3Median) {
  448. box = new Rectangle2D.Double(
  449. xx - width / 2, yyQ3Median, width, yyQ1Median - yyQ3Median
  450. );
  451. }
  452. else {
  453. box = new Rectangle2D.Double(
  454. xx - width / 2, yyQ1Median, width, yyQ3Median - yyQ1Median
  455. );
  456. }
  457. if (this.fillBox) {
  458. g2.fill(box);
  459. }
  460. g2.draw(box);
  461. // draw median
  462. g2.setPaint(getArtifactPaint());
  463. g2.draw(new Line2D.Double(xx - width / 2, yyMedian, xx + width / 2, yyMedian));
  464. double aRadius = 0; // average radius
  465. double oRadius = width / 3; // outlier radius
  466. // draw average - SPECIAL AIMS REQUIREMENT
  467. if (yAverage != null) {
  468. aRadius = width / 4;
  469. Ellipse2D.Double avgEllipse = new Ellipse2D.Double(
  470. xx - aRadius, yyAverage - aRadius, aRadius * 2, aRadius * 2
  471. );
  472. g2.fill(avgEllipse);
  473. g2.draw(avgEllipse);
  474. }
  475. List outliers = new ArrayList();
  476. OutlierListCollection outlierListCollection = new OutlierListCollection();
  477. /* From outlier array sort out which are outliers and put these into an arraylist
  478. * If there are any farouts, set the flag on the OutlierListCollection
  479. */
  480. for (int i = 0; i < yOutliers.size(); i++) {
  481. double outlier = ((Number) yOutliers.get(i)).doubleValue();
  482. if (outlier > boxAndWhiskerData.getMaxOutlier(series, item).doubleValue()) {
  483. outlierListCollection.setHighFarOut(true);
  484. }
  485. else if (outlier < boxAndWhiskerData.getMinOutlier(series, item).doubleValue()) {
  486. outlierListCollection.setLowFarOut(true);
  487. }
  488. else if (outlier > boxAndWhiskerData.getMaxRegularValue(series, item).doubleValue()) {
  489. yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, location);
  490. outliers.add(new Outlier(xx, yyOutlier, oRadius));
  491. }
  492. else if (outlier < boxAndWhiskerData.getMinRegularValue(series, item).doubleValue()) {
  493. yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, location);
  494. outliers.add(new Outlier(xx, yyOutlier, oRadius));
  495. }
  496. Collections.sort(outliers);
  497. }
  498. // Process outliers. Each outlier is either added to the appropriate outlier list
  499. // or a new outlier list is made
  500. for (Iterator iterator = outliers.iterator(); iterator.hasNext();) {
  501. Outlier outlier = (Outlier) iterator.next();
  502. outlierListCollection.add(outlier);
  503. }
  504. // draw yOutliers
  505. double maxAxisValue = rangeAxis.valueToJava2D(
  506. rangeAxis.getUpperBound(), dataArea, location
  507. ) + aRadius;
  508. double minAxisValue = rangeAxis.valueToJava2D(
  509. rangeAxis.getLowerBound(), dataArea, location
  510. ) - aRadius;
  511. // draw outliers
  512. for (Iterator iterator = outlierListCollection.iterator(); iterator.hasNext();) {
  513. OutlierList list = (OutlierList) iterator.next();
  514. Outlier outlier = list.getAveragedOutlier();
  515. Point2D point = outlier.getPoint();
  516. if (list.isMultiple()) {
  517. drawMultipleEllipse(point, width, oRadius, g2);
  518. }
  519. else {
  520. drawEllipse(point, oRadius, g2);
  521. }
  522. }
  523. // draw farout
  524. if (outlierListCollection.isHighFarOut()) {
  525. drawHighFarOut(aRadius, g2, xx, maxAxisValue);
  526. }
  527. if (outlierListCollection.isLowFarOut()) {
  528. drawLowFarOut(aRadius, g2, xx, minAxisValue);
  529. }
  530. // add an entity for the item...
  531. if (entities != null) {
  532. String tip = null;
  533. XYToolTipGenerator generator = getToolTipGenerator(series, item);
  534. if (generator != null) {
  535. tip = generator.generateToolTip(dataset, series, item);
  536. }
  537. String url = null;
  538. if (getURLGenerator() != null) {
  539. url = getURLGenerator().generateURL(dataset, series, item);
  540. }
  541. XYItemEntity entity = new XYItemEntity(box, dataset, series, item, tip, url);
  542. entities.add(entity);
  543. }
  544. }
  545. /**
  546. * Draws an ellipse to represent an outlier.
  547. *
  548. * @param point the location.
  549. * @param oRadius the radius.
  550. * @param g2 the graphics device.
  551. */
  552. protected void drawEllipse(Point2D point, double oRadius, Graphics2D g2) {
  553. Ellipse2D.Double dot = new Ellipse2D.Double(
  554. point.getX() + oRadius / 2, point.getY(), oRadius, oRadius
  555. );
  556. g2.draw(dot);
  557. }
  558. /**
  559. * Draws two ellipses to represent overlapping outliers.
  560. *
  561. * @param point the location.
  562. * @param boxWidth the box width.
  563. * @param oRadius the radius.
  564. * @param g2 the graphics device.
  565. */
  566. protected void drawMultipleEllipse(Point2D point, double boxWidth, double oRadius,
  567. Graphics2D g2) {
  568. Ellipse2D.Double dot1 = new Ellipse2D.Double(
  569. point.getX() - (boxWidth / 2) + oRadius, point.getY(), oRadius, oRadius
  570. );
  571. Ellipse2D.Double dot2 = new Ellipse2D.Double(
  572. point.getX() + (boxWidth / 2), point.getY(), oRadius, oRadius
  573. );
  574. g2.draw(dot1);
  575. g2.draw(dot2);
  576. }
  577. /**
  578. * Draws a triangle to indicate the presence of far out values.
  579. *
  580. * @param aRadius the radius.
  581. * @param g2 the graphics device.
  582. * @param xx the x value.
  583. * @param m the max y value.
  584. */
  585. protected void drawHighFarOut(double aRadius, Graphics2D g2, double xx, double m) {
  586. double side = aRadius * 2;
  587. g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side));
  588. g2.draw(new Line2D.Double(xx - side, m + side, xx, m));
  589. g2.draw(new Line2D.Double(xx + side, m + side, xx, m));
  590. }
  591. /**
  592. * Draws a triangle to indicate the presence of far out values.
  593. *
  594. * @param aRadius the radius.
  595. * @param g2 the graphics device.
  596. * @param xx the x value.
  597. * @param m the min y value.
  598. */
  599. protected void drawLowFarOut(double aRadius, Graphics2D g2, double xx, double m) {
  600. double side = aRadius * 2;
  601. g2.draw(new Line2D.Double(xx - side, m - side, xx + side, m - side));
  602. g2.draw(new Line2D.Double(xx - side, m - side, xx, m));
  603. g2.draw(new Line2D.Double(xx + side, m - side, xx, m));
  604. }
  605. /**
  606. * Tests this renderer for equality with another object.
  607. *
  608. * @param obj the object.
  609. *
  610. * @return <code>true</code> or <code>false</code>.
  611. */
  612. public boolean equals(Object obj) {
  613. if (obj == this) {
  614. return true;
  615. }
  616. if (!(obj instanceof XYBoxAndWhiskerRenderer)) {
  617. return false;
  618. }
  619. if (!super.equals(obj)) {
  620. return false;
  621. }
  622. XYBoxAndWhiskerRenderer that = (XYBoxAndWhiskerRenderer) obj;
  623. if (this.boxWidth != that.getBoxWidth()) {
  624. return false;
  625. }
  626. if (!this.boxPaint.equals(that.getBoxPaint())) {
  627. return false;
  628. }
  629. if (!ObjectUtilities.equal(this.artifactPaint, that.artifactPaint)) {
  630. return false;
  631. }
  632. if (this.fillBox != that.fillBox) {
  633. return false;
  634. }
  635. return true;
  636. }
  637. /**
  638. * Provides serialization support.
  639. *
  640. * @param stream the output stream.
  641. *
  642. * @throws IOException if there is an I/O error.
  643. */
  644. private void writeObject(ObjectOutputStream stream) throws IOException {
  645. stream.defaultWriteObject();
  646. SerialUtilities.writePaint(this.boxPaint, stream);
  647. SerialUtilities.writePaint(this.artifactPaint, stream);
  648. }
  649. /**
  650. * Provides serialization support.
  651. *
  652. * @param stream the input stream.
  653. *
  654. * @throws IOException if there is an I/O error.
  655. * @throws ClassNotFoundException if there is a classpath problem.
  656. */
  657. private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
  658. stream.defaultReadObject();
  659. this.boxPaint = SerialUtilities.readPaint(stream);
  660. this.artifactPaint = SerialUtilities.readPaint(stream);
  661. }
  662. /**
  663. * Returns a clone of the renderer.
  664. *
  665. * @return A clone.
  666. *
  667. * @throws CloneNotSupportedException if the renderer cannot be cloned.
  668. */
  669. public Object clone() throws CloneNotSupportedException {
  670. return super.clone();
  671. }
  672. }